ES next中async/await proposal實現原理是什麼?

1.在Javascript中如何實現的async/await特性

2.相比C#中對async/await的實現,Javascript的實現有何不同


如同cat chen老師說的,正好之前有天在看sweet.js,大概試了下用宏做cps變換,後來看到有人直接基於sweet.js + promise寫了async await的宏。

效果就是可以將以下邏輯描述

async function findPosts() {
var response = await $.get("/posts");
try{
return JSON.parse(response.posts)
} catch(e) {
throw new Error("failed")
}
}

轉譯為

function findPosts() {
var ctx = this, args = arguments;
return Promise.resolve().then(function () {
var response;
return $.get("/posts").then(function (value) {
response = value;
return Promise.resolve().then(function () {
return JSON.parse(response.posts);
}).catch(function (e) {
throw new Error("failed");
}).then(function () {
});
});
});
}

try-catch 部分的宏表述如下:

let (try) = macro {
rule { $tryBody catch $catchParams $catchBody finally $finallyBody $after $[...] } =&> {
return Promise.resolve()
.then(function () $tryBody)
.catch(function $catchParams $catchBody)
.finally(function () $finallyBody)
.then(function () {
$after $[...]
});
}

rule { $tryBody catch $catchParams $catchBody $after $[...] } =&> {
return Promise.resolve()
.then(function () $tryBody)
.catch(function $catchParams $catchBody)
.then(function () {
$after $[...]
});
}
}

基本上就達到了同步轉非同步的行為了。

具體實現請看: jayphelps/sweet-async-await · GitHub


babel和typescript的async/await是用yield/generator實現的,目前似乎還沒有引擎原生支持async/await。

至於引擎里,實現的方法有很多種,比如CPS變換是一種,就是把

tmp = await a(input)
return b(tmp)
// 順序執行風格

改寫

return first_do_then_do(a, input, b) // 回調函數風格

那有多句順序執行代碼呢?就疊羅漢唄,這種方式的話,順序代碼的終極形態就是「回調金字塔」

再比如babel-transform-regenerator實現的方式,把每一句yield都變成一個狀態,然後用狀態機的方式去實現

再比如可以引擎保存yield出讓執行許可權的位置和棧上的數據快照,然後再next()交還執行許可權的時候恢複位置和棧,我猜想引擎應該傾向於這樣實現的吧


本質上是 generator。

V8 更換了新的優化執行引擎為 TurboFan 後,不僅僅可以優化 try/catch/finally、for...of 等之前 Crankshaft 不能優化的語法,還支持了更多的 ES2015+ features。

並不是新的 TurboFan 引擎增加了對 async 的支持,而是增加了對 async、generators 優化的支持。

Generators had long been supported by V8, but were not optimizable due to control flow limitations in Crankshaft. Async functions are essentially sugar on top of generators, so they fall into the same category. The new compiler pipeline leverages Ignition to make sense of the AST and generate bytecodes which de-sugar complex generator control flow into simpler local-control flow bytecodes. TurboFan can more easily optimize the resulting bytecodes since it doesn』t need to know anything specific about generator control flow, just how to save and restore a function』s state on yields.

下面是生成的二進位碼和 TurboFan 執行圖:

參考資料:

  • V8』s new interpreter and compiler pipeline

  • High-performance ES2015 and beyond


ES7是個標準,定義的是what to do不是how to do,為什麼好多人還是搞不清這兩者的區別。


本質上就是個 Promise。所有 async 函數都返回 Promise,所有 await 都相當於把之後的代碼放在 then callback 里,而 try-catch 要變為 then 的 rejection callback。


本質上不是promise,而是yield。

原理是continuation。

yield+promise只是其中的一種玩法。

我們來分析一下:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

(1)先看一下async和await:

async function say(greeting){
return new Promise(function(resolve,then){
setTimeout(function(){
resolve(greeting);
},1500);
});
}

(async function(){
var v1=await say("Hello");
console.log(v1);

var v2=await say("World");
console.log(v2);
}());

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

(2)沒錯,yield+promise是可以實現它:

yieldPromise(function*(){
var v1=yield new Promise(function(resolve,reject){
setTimeout(function(){
resolve("Hello");
},1500);
});

console.warn(v1);

var v2=yield new Promise(function(resolve,reject){
setTimeout(function(){
resolve("World");
},1500);
});

console.warn(v2);
});

實現方法:

function yieldPromise(generator){
var iterator=generator();
recursiveCore.call(iterator);
}

function recursiveCore(feedback){
var iterator=this,
result=iterator.next(feedback);

if(result.done){
return;
}

var promise=result.value;
Promise.resolve(promise).then(function(v){
recursiveCore.call(iterator,v);
});
}

這個在Chrome中是可以運行的:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

然而,我們能不能把promise去掉呢?

能不能,讓promise的使用,成為一般情況下的一種特例呢?

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

(3)是可以的。

剛好其他語言有continuation這個概念(關於call/cc的橋段,略),

而且generator(yield)也可以看做這一思想在JavaScript中的應用。

看看用例:

yieldContinuation(function*(){
var v1=yield function(k){
setTimeout(function(){
k("Hello");
},1500);
};

console.warn(v1);

var v2=yield function(k){
setTimeout(function(){
k("World");
},1500);
};

console.warn(v2);
});

實現方法:

function yieldContinuation(generator){
var iterator=generator();
recursiveCore.call(iterator);
}

function recursiveCore(feedback){
var iterator=this,
result=iterator.next(feedback);

if(result.done){
return;
}

var yieldFunc=result.value;
yieldFunc(function(v){
recursiveCore.call(iterator,v);
});
}

這個在Chrome中是也可以運行的:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

(4)然後就可以了。

玩一下promise:

yieldContinuation(function*(){
var v1=yield function(k){
new Promise(function(resolve,reject){
setTimeout(function(){
resolve("Hello");
},1500);
}).then(k);
};

console.warn(v1);

var v2=yield function(k){
new Promise(function(resolve,reject){
setTimeout(function(){
resolve("World");
},1500);
}).then(k);
};

console.warn(v2);
});

這個當然必須也是可以運行的了:

- - - - - - - - - - - - - - - - - - - - -玩一下ajax:(sendAjax的實現,略)

yieldContinuation(function*(){
var v1=yield function(k){
sendAjax({
url:"/",
success:k
});
};

console.warn(v1);

var v2=yield function(k){
sendAjax({
url:"/",
success:k
});
};

console.warn(v2);
});

這個在實現了sendAjax後,當然也是可以運行的。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

酸爽。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

JS高級前端開發 159758989


補充一下 @Cat Chen 和 @Saviio 的答案

有一個叫 fast-async 的 babel 6 plugin,是用 nodent 編譯的,可以將 async/await 生成 IE8 可用的代碼(需加 Promise polyfill)。

對於項目里附帶的測試用例,我用 bluebird@2, bluebird@3, es6-promise 在 IE, firefox, chrome 以及用原生 Promise 在 firefox 和 chrome 跑過,結果是 es6-promise 與原生差不多,而 bluebird@3 奇慢,耗時能達到 chrome 原生的 10倍!一般也至少在 3 倍時間,所以對 bluebird 作者吹的 chrome 原生 Promise 性能永遠趕不上 bluebird 很是懷疑。但是換用 bluebird@2, 卻又比 firefox 和 chrome 的原生 Promise 能快上 20-30%。

所以結論是 async/await 現在就可以用在加了 bluebird@2 的所有 IE8+ 環境,這樣 generator 反而是個只能在伺服器端用的特性了。


實名反對那個說用yield實現的 瀏覽器由內部機制實現 只是和yield實現效果類似


等Typescript 2.0發布了就可以看到TS是怎麼用ES3實現 async await了(我猜還是Promise),目前版本(1.7.5)是 yield + Promise.


es7 的 async/await 是對 yield 的簡單封裝

es6 的 GeneratorFunction/yield 是在 vm 做的,本質是保存棧幀


猜測應該就是generator+promise+執行器的語法糖。


可以先去看看co的使用方法和源碼,然後就能明白這玩意是個啥


用promise 封裝的generator自動執行函數。參考co源碼


ES的底層應該用的是協程 就是還是單線程的

c#的要看相應的xxxasync函數怎麼實現的 有一些用的線程池 就是開了新的線程去跑 有一些好像跟協程差不多


參考co源碼。promise+yield的語法糖


推薦閱讀:

angular directive 等controller執行完後再執行?
一個優秀的前端工程師簡歷應該是怎樣的?
如何劃分前端技術階段?
HTML 靜態頁面的頭部和底部都是相同的,如何讓每個頁面統一調用一個公共的頭部和底部呢?
關於angualr網站後台?

TAG:前端開發 | JavaScript |