背景
我司做了一個可視化大屏的項目,但基于配置的組件不可能窮盡所有情況,因此計劃開發(fā)js交互功能,讓用戶可以通過js進行二次開發(fā)。
遇到的問題
直接用new Function()去執(zhí)行用戶的代碼,一開始寫靜態(tài)數(shù)據(jù)demo的時候沒發(fā)現(xiàn)有啥問題。但當我去請求動態(tài)數(shù)據(jù)的時候,發(fā)現(xiàn)我很想用await,然而直接使用會報SyntaxError: await is only valid in async function at new Function (<anonymous>)的錯。
曲折的解決問題
step1
google查詢如何在new Function里使用async,查到了這篇文章,大喜。主要代碼:
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
const fetchPage = new AsyncFunction("url", "return await fetch(url);");
fetchPage("/").then(response => { ... });
因為原生不支持直接獲取AsyncFunction,所以這里通過獲取async函數(shù)的構造函數(shù)原型來獲取。
屁顛屁顛地跑去項目里用了一下,發(fā)現(xiàn)并沒有生效。
step2
開始脫離項目寫demo,把問題最小化。代碼如下:
function test() {
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
const code =
'const name = await Promise.resolve(stage.name); \n console.log(name)';
const func = new AsyncFunction('stage', code);
func({ name: '1' }).then((response) => {
console.log(response);
});
}
test();
在node.js和chrome環(huán)境下均可以正確執(zhí)行
step3
思考項目中用到了什么,在想不會是webpack的鍋吧,但不能確定。
于是回到項目,在瀏覽器報錯后跳轉到報錯的地方,是一個VMxxxx文件,截屏如下:

看到我自己的code前面被包了一層(function anonymous(){mycode}),猜想會不會跟這個有關,但查了一下VMxxxx文件是js虛擬機編譯生成的文件,照理說瀏覽器對同樣的代碼執(zhí)行流程都一樣,肯定都會經歷編譯階段,既然在瀏覽器直接執(zhí)行不報錯,應該跟這個無關。
接著打斷點看看發(fā)生了什么。在報錯的語句前打了斷點,然后在瀏覽器定義AsyncFunction并執(zhí)行,并沒有報錯。


正當我納悶的時候,想著應該對比一下項目里的AsyncFunction和我直接在瀏覽器定義的AsyncFunction有什么區(qū)別,就分別打印了一下,終于找到了原因,項目里的AsyncFunction不知道啥時候悄悄變成了Function,怪不得又不能用await了。

step4
所以思考AsyncFunction為什么會變成Function。似乎只有webpack可能產生影響,于是在另一個用webpack打包的項目中嘗試了一下,也報一樣的錯,基本上鎖定是webpack的問題了。想到我們一般在項目里會用babel把js編譯成es5,而es5不認識async,自然也不認識它的原型,所以我把AsyncFunction的定義放到一個單獨的文件,不讓webpack編譯,終于可以順利執(zhí)行了!
結論
- 如果在原生支持es7的項目(不經過babel等轉義)中使用,直接通過
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;獲取到原型,就可以像new Function一樣使用了 - 目前前端工程化項目為了兼容性,一般都會用到babel轉義,這時候就需要先把
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;這個定義放到單獨的文件,并且不轉義該文件,才能在執(zhí)行的時候獲取正確的類型。