都說async/await
是Generator
+Promise
的語法糖,經過本文逐步揭開async/await
背後的祕密...react
Generator
把異步邏輯同步化function asyncOp(x) { return new Promise((resolve, reject) => { setTimeout(() => { Number.isSafeInteger(x) ? resolve(12) : reject(new Error('Invalid integer')); }, 3000) }) } function *gen(x) { try { var y = yield asyncOp(x); return x + y; } catch(e) { console.error(e) } } var g = gen(1); // 獲取異步操做 var asyncAction = g.next(); asyncAction.value .then(value => { // 把異步操做的結果值傳給生成器函數 var result = g.next(value); console.log(result.value); })
總體思路:git
yield
返回異步操做(並暫停生成器函數執行);next
方法把異步操做的結果值傳入生成器函數(並繼續執行生成器函數);yield
表達式的值是同步仍是異步。throw
方法在暫定位置拋異常。var g = gen('a'); var asyncAction = g.next(); asyncAction.value .catch(reason => { // 經過throw告訴生成器函數異常操做發生了異常 g.throw(reason); })
function getRadom() { return (Math.random() * 100) >>> 0; } function getRandomAsync() { return new Promise(resolve => { setTimeout(() => { resolve(getRadom()); }, 2000) }) } function* sum() { var x = yield getRandomAsync(); console.log(`x=${x}`) var y = yield getRandomAsync(); console.log(`y=${y}`) return x + y; }
var gen = sum(); // 獲取異步操做1 gen.next().value .then(val => { // 把異步操做1的結果傳給生成器函數,並獲取異步操做2 gen.next(val).value .then(val => { // 把異步操做2的結果傳給生成器函數,並獲最終結果 var sum = gen.next(val).value; console.log(`sum=${sum}`) }) })
上面的寫法就像記流水帳,若是有3個數相加還這樣寫豈不是要瘋。github
// 生成器函數執行器 function runner(genFunc) { // 建立生成器對象 var gen = genFunc(); // 開啓執行 return doRun(); function doRun(arg) { // 把上一個異步操做結果`arg`傳如生成器函數 var data = gen.next(arg); if(data.done) { return Promise.resolve(data.value); } // 若是還沒結束,就等異步操做結束後遞歸調用doRun。 return Promise.resolve(data.value).then(doRun); } } runner(sum).then(sum => { console.log(sum) })
注意:
這裏使用Promise.resolve
方法把data.value
轉成Promise
,由於Promise.resolve
的特殊功能:若是實參value是個Promise對象,則直接返回實參babel
function runner(genFunc) { var gen = genFunc(); return new Promise((resolve, reject) => { doRun(); function doRun(arg) { try { // 捕獲`next`方法拋出的異常 var data = gen.next(arg); if(data.done) { return resolve(data.value); } // 若是還沒結束,就等異步操做結束後遞歸調用doRun。 return Promise.resolve(data.value) .then(doRun) .catch(gen.throw) // 經過`throw`方法告訴生成器異常了 .catch(reject) // 捕獲`throw`方法拋出的異常(即生成器方法沒有捕獲處理異常) } catch(error) { reject(error); } } }) } runner(sum).then(sum => { console.log(sum) }) .catch(reason => { console.error(reason) })
throw
方法,next
方法也可能會拋出異常,因此在最外層使用try-catch
捕獲next
方法拋出的異常;runner
方法的返回值也再也不是doRun()
了,而改爲了Promise
,用於處理next
方法拋出的異常和最終的結果值。捕獲throw
方法拋出的異常也能夠採用try-catch
方式,這樣就跟捕獲next
方法拋出的異常保持一致了:app
function runner(genFunc) { var gen = genFunc(); return doRun(); function doRun(arg) { return new Promise((resolve, reject) => { step('next'); function step(methodName, arg) { try { // 捕獲`next`方法拋出的異常 var data = gen[methodName](arg); } catch(error) { reject(error); return; } if(data.done) { return resolve(data.value); } return Promise.resolve(data.value) .then(value => { step('next', value); }) .catch(reason => { step('throw', reason); }) } }) } } runner(sum) .then(sum => { console.log(sum) }) .catch(reason => { console.error(reason) })
async/await
的Generator
+Promise
寫法function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg){ try{ var info = gen[key](arg); var value = info.value; }catch(error){ reject(error); return; } if(info.done){ resolve(value); }else{ Promise.resolve(value).then(_next, _throw); } } // 負責把`async`轉成`generator` function _asyncToGenerator(fn) { return function () { // 處理傳給生成器的參數 var self = this, args = arguments; return new Promise(function (resolve, reject){ // 生成器的函數在Promise參數的回調函數裏執行,而且處理參數 var gen = fn.apply(self, args); function _next(value){ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err){ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
使用固定的模式把async
轉成Generator
,Babel的實現更嚴謹些:異步
Generator
,調用時再傳參。參考這個調整以前的實現:async
function _asyncToGenerator(genFunc) { return function() { var args = arguments; var self = this; return new Promise((resolve, reject) => { var gen = genFunc.apply(self, args); step('next'); function step(methodName, arg) { try { var data = gen[methodName](arg); } catch(error) { reject(error); return; } if(data.done) { return resolve(data.value); } return Promise.resolve(data.value) .then(value => { step('next', value); }) .catch(reason => { step('throw', reason); }) } }) } } _asyncToGenerator(sum)() .then(sum => { console.log(sum) }) .catch(reason => { console.error(reason) })
總結下async/await
轉成Genertor
方式:函數
await
直接替換成yield
;async
函數體代碼轉成生成器代碼(匿名的生成器函數);async
函數名被轉成普通函數內部調用生成器函數的函數。async function sum() { var x = await 1; var y = await 2; return x + y; } // 對應的生成器方式 function sum() { return _sum.apply(this, arguments); } function _sum() { // 注意:函數體內的_sum變量是個局部變量,不影響外部做用域下的_sum的值。 _sum = _asyncToGenerator(function* () { var x = yield 1; var y = yield 2; return x + y; }); // 建立生成器對象,並開始執行生成器 return _sum.apply(this, arguments); }