本做品採用知識共享署名 4.0 國際許可協議進行許可。轉載聯繫做者並保留聲明頭部與原文連接https://luzeshu.com/blog/es-async
本博客同步在http://www.cnblogs.com/papertree/p/7152462.htmljavascript
把異步執行的函數放進回調函數中是最原始的作法。
可是異步的層次太多時,出現的回調嵌套致使代碼至關難看而且難以維護。html
taskAsyncA(function () { taskAsyncB(function () { taskAsyncC(function () { ... }) }); });
因而出現了不少異步流程控制的包。說白了就是把多層嵌套的異步代碼展平了。
如async.js 和 bluebird/Promise。java
async.series(function (cb) { taskAsyncA(function () { ... return cb(); }); }, function(cb) { taskAsyncB(function () { return cb(); }); }, function(cb) { taskAsyncC(function () { return cb(); }); .... }, function (err) { });
taskPromisifyA = Promise.promisify(taskAsyncA); taskPromisifyB = Promise.promisify(taskAsyncB); taskPromisifyC = Promise.promisify(taskAsyncC); ..... Promise.resolve() .then(() => taskPromisifyA()) .then(() => taskPromisifyB()) .then(() => taskPromisifyC()) ......
es6標準多了一些新語法Generator函數、Iterator對象、Promise對象、yield語句。
es6的Promise對象是原生的,不依賴bluebird這些包。node
下面展現了定義一個Generator函數的語法。
調用Generator函數時返回一個Iterator迭代器。經過該迭代器可以不斷觸發Generator函數裏面的yield步驟。
iter.next()返回的是一個含有value、done屬性的對象,done表示是否到達結尾,value表示yield的返回值。
這裏須要注意的是,Generator函數調用時返回一個Iterator,可是自己的代碼是中止的,等iter.next()纔會開始執行。es6
2 function* gen() { 3 console.log('step 1'); 4 yield 'str 1'; 5 console.log('step 2'); 6 yield; 7 yield; 8 return 'str 2'; 9 } 10 11 let iter = gen(); 12 console.log(iter.contructor); 13 console.log('start!'); 14 console.log(iter.next()); 15 console.log(iter.next()); 16 console.log(iter.next()); 17 console.log(iter.next()); 18 console.log(iter.next());
輸出:express
[Sherlock@Holmes Moriarty]$ node app.js undefined start! step 1 { value: 'str 1', done: false } step 2 { value: undefined, done: false } { value: undefined, done: false } { value: 'str 2', done: true } { value: undefined, done: true }
若是在Generator函數裏面,再yield一個generator函數或者Iterator對象,實際上不會串聯到一塊兒。看一下下面的例子就明白了。數組
1 function* gen2() { 2 console.log('gen2: step1'); 3 yield 'str3 in gen2'; 4 console.log('gen2: ste2'); 5 yield; 6 yield; 7 return 'str4 in gen2'; 8 } 9 10 function* gen() { 11 console.log('step 1'); 12 yield 'str 1'; 13 console.log('step 2'); 14 yield gen2(); 15 yield; 16 return 'str 2'; 17 } 18 19 let iter = gen(); 20 console.log(iter.contructor); 21 console.log('start!'); 22 console.log(iter.next()); 23 console.log(iter.next()); 24 console.log(iter.next()); 25 console.log(iter.next()); 26 console.log(iter.next());
與例子1的輸出基本同樣。第14行代碼所執行的,僅僅是gen2()返回了一個普通的Iterator對象,再被yield當成普通的返回值返回了而已。因此該行輸出的value是一個{}。promise
一樣的,把第14行的「yield gen2()」修改爲「yield gen2」。那麼也只是把gen2函數當成一個普通的對象返回了。對應的輸出是:app
{ value: [GeneratorFunction: gen2], done: false }
那麼咱們在用koa@1的時候,常常有「yield next」(等效於「yield* next」),這個next實際上就是一個 對象。它所達到的效果,是經過 實現的。下篇博客再講。koa
yield* 後面跟着一個可迭代對象(iterable object)。包括Iterator對象、數組、字符串、arguments對象等等。
若是但願兩個Generator函數串聯到一塊兒,應該把例子2中的第14行代碼「yield gen2()」改爲「yield* gen2()」。此時的輸出爲:
[Sherlock@Holmes Moriarty]$ node app.js undefined start! step 1 { value: 'str 1', done: false } step 2 gen2: step1 { value: 'str3 in gen2', done: false } gen2: ste2 { value: undefined, done: false } { value: undefined, done: false } { value: undefined, done: false } { value: 'str 2', done: true } { value: undefined, done: true } { value: undefined, done: true }
但gen2()return的'str4 in gen2'沒有被輸出。當把14行代碼再次改爲「console.log(yield* gen2())」時,纔會把return回來的結果輸出,並且也不一樣於yield 返回的對象類型。
輸出結果:
[Sherlock@Holmes Moriarty]$ node app.js undefined start! step 1 { value: 'str 1', done: false } step 2 gen2: step1 { value: 'str3 in gen2', done: false } gen2: ste2 { value: undefined, done: false } { value: undefined, done: false } str4 in gen2 { value: undefined, done: false } { value: 'str 2', done: true } { value: undefined, done: true } { value: undefined, done: true }
關於yield*語句的說明:
The yield* expression iterates over the operand and yields each value returned by it.
The value of yield* expression itself is the value returned by that iterator when it's closed (i.e., when done is true).
(來自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*)
若是在Generator函數裏面yield一個Promise對象。一樣不會有任何特殊的地方,Promise對象會被yield返回,而且輸出"value: Promise {
例子代碼:
1 function pro() { 2 return new Promise((resolve) => { 3 console.log('waiting...'); 4 setTimeout(() => { 5 console.log('timeout'); 6 return resolve(); 7 }, 3000); 8 }); 9 } 10 11 function* gen() { 12 console.log('step 1'); 13 yield 'str 1'; 14 console.log('step 2'); 15 yield pro(); 16 yield; 17 return 'str 2'; 18 } 19 20 let iter = gen(); 21 console.log(iter.contructor); 22 console.log('start!'); 23 console.log(iter.next()); 24 console.log(iter.next()); 25 console.log(iter.next()); 26 console.log(iter.next()); 27 console.log(iter.next());
輸出:
[Sherlock@Holmes Moriarty]$ node app.js undefined start! step 1 { value: 'str 1', done: false } step 2 waiting... { value: Promise { <pending> }, done: false } { value: undefined, done: false } { value: 'str 2', done: true } { value: undefined, done: true } timeout
執行時在{value: undefined, done: true}和timeout之間等待了3秒。
上面四個例子大概展現了es6的Generator和Iterator語法的特性。
相似於提供了咱們一個狀態機的支持。
但這裏有兩個問題:
當用co庫時:
例子5:
2 const co = require('co'); // 4.6.0版本 3 function pro() { 4 return new Promise((resolve) => { 5 console.log('waiting...'); 6 setTimeout(() => { 7 console.log('timeout'); 8 return resolve(); 9 }, 3000); 10 }); 11 } 12 13 function* gen() { 14 console.log('step 1'); 15 // yield 'str 1'; 16 console.log('step 2'); 17 yield pro(); 18 console.log('step 3'); 19 // yield; 20 return 'str 2'; 21 } 22 23 co(gen);
輸出:
[Sherlock@Holmes Moriarty]$ node app.js step 1 step 2 waiting... timeout step 3
能夠看出'step 3'的輸出等到promise被settle以後才執行。
例子6:
若是取消第15行代碼註釋,yield 一個字符串或者undefined等,則報錯:
[Sherlock@Holmes Moriarty]$ node app.js step 1 (node:29050) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "str 1" (node:29050) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
在1.2.2的例子2中作過一個試驗,第14行代碼yield了gen2()返回的Iterator對象以後,gen2()並不會被執行,而且yield gen2()輸出的值僅僅只是「value: {}, done: false」這樣的普通對象。
而若是經過yield* gen2(),在1.2.3中的例子能夠看到是會執行gen2()的。
可是在koa1中的中間件裏面,「yield* next」和「yield next」是同樣的效果,都可以讓中間件鏈繼續往下執行。
這裏面的緣由正是koa1依賴的co庫作了處理。
在co裏面,yield一個Iterator對象和yield* 一個Iterator對象,效果是同樣的。
例子7:
1 const co = require('co'); 2 3 function* gen2() { 4 console.log('gen2: step1'); 5 return 'str4 in gen2'; 6 } 7 8 function* gen() { 9 console.log('step 1'); 10 yield *gen2(); 11 console.log('step 2'); 12 return 'str 2'; 13 } 14 15 co(gen);
上面那個異常怎麼拋出的呢?能夠來跟蹤一下co源碼流程。co源碼至關小。
function co(gen) { var ctx = this; var args = slice.call(arguments, 1) return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }); } function toPromise(obj) { if (!obj) return obj; if (isPromise(obj)) return obj; if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); if ('function' == typeof obj) return thunkToPromise.call(this, obj); if (Array.isArray(obj)) return arrayToPromise.call(this, obj); if (isObject(obj)) return objectToPromise.call(this, obj); return obj; }
co()的參數能夠是Generator函數也能夠是返回Promise對象的函數。
若是是Generator函數,返回了Iterator對象,進入到onFulfilled(),並進入「永動機」的環節。
每一次yield回來的東西調用next,若是是不容許的類型(好比string、undefined等),就會產生一個TypeError並進入onRejected()。
若是是Proise對象,就等待settle。若是是Generator函數,就繼續用co包裝……
若是咱們yield 回去的promise對象、或者co本身產生的TypeError,最終都去到onRejected(err)。
1.2 說了Generator本質上有點相似狀態機,yield 一個promise對象自己不會等待該promise被settle,也天然沒法等待一個異步回調。而co庫利用Generator特性去實現了。
在es7的新特性中,引入了async函數和await語句。await語句生來就是用來等待一個Promise對象的。並且await語法返回值是該Promise對象的resolve值。見下面例子:
The await operator is used to waiting for a Promise. It can only be used inside an async function.
(來自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)
例子:
2 function async1() { 3 return new Promise((resolve) => { 4 console.log('waiting...'); 5 setTimeout(() => { 6 console.log('timeout'); 7 return resolve('resolve value'); 8 }, 3000); 9 }); 10 } 11 12 (async function () { 13 let ret = await async1(); 14 console.log(ret); 15 })();
輸出:
[Sherlock@Holmes Moriarty]$ node app.js waiting... timeout resolve value
此外,async函數被執行時同普通函數同樣,自動往下執行。而不像Generator函數須要一個Iterator對象來激發。