本做品采用知识共享署名 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对象来激发。