上篇:es5、es6、es7中的异步写法
1.1 es5 —— 回调
把异步执⾏的函数放进回调函数中是最原始的做法。
但是异步的层次太多时,出现的回调嵌套导致代码相当难看并且难以维护。
taskAsyncA(function () {
taskAsyncB(function () {
taskAsyncC(function () {
...
})
});
});
于是出现了很多异步流程控制的包。说⽩了就是把多层嵌套的异步代码展平了。
如async.js 和 bluebird/Promise。
1.1.1 async
async.series(function (cb) {
taskAsyncA(function () {
...
return cb();
});
}, function(cb) {
taskAsyncB(function () {
return cb();
});
}, function(cb) {
taskAsyncC(function () {
return cb();
});
....
}, function (err) {
});
1.1.2 bluebird/Promise
taskPromisifyA = Promise.promisify(taskAsyncA);
taskPromisifyB = Promise.promisify(taskAsyncB);
taskPromisifyC = Promise.promisify(taskAsyncC);
.....
.then(() => taskPromisifyA())
.then(() => taskPromisifyB())
.then(() => taskPromisifyC())
......
1.2 es6/es2015 —— generator函数和yield
es6标准多了⼀些新语法Generator函数、Iterator对象、Promise对象、yield语句。
es6的Promise对象是原⽣的,不依赖bluebird这些包。
1.2.1 例⼦1
下⾯展⽰了定义⼀个Generator函数的语法。
调⽤Generator函数时返回⼀个Iterator迭代器。通过该迭代器能够不断触发Generator函数⾥⾯的yield步骤。
<()返回的是⼀个含有value、done属性的对象,done表⽰是否到达结尾,value表⽰yield的返回值。
这⾥需要注意的是,Generator函数调⽤时返回⼀个Iterator,但是本⾝的代码是停⽌的,等()才会开始执⾏。
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.uctor);
13 console.log('start!');
14 console.());
15 console.());
16 console.());
17 console.());
18 console.());
输出:
[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 }
1.2.2 例⼦2
如果在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.uctor);
21 console.log('start!');
22 console.());
23 console.());
24 console.());
25 console.());
26 console.());
与例⼦1的输出基本⼀样。第14⾏代码所执⾏的,仅仅是gen2()返回了⼀个普通的Iterator对象,再被yield当成普通的返回值返回了⽽已。所以该⾏输出的value是⼀个{}。
同样的,把第14⾏的“yield gen2()”修改成“yield gen2”。那么也只是把gen2函数当成⼀个普通的对象返回了。对应的输出是:
{ value: [GeneratorFunction: gen2], done: false }
那么我们在⽤koa@1的时候,经常有“yield next”(等效于“yield* next”),这个next实际上就是⼀个对象。它所达到的效果,是通过实现的。下篇博客再讲。
1.2.3 例⼦3 yield*
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).
1.2.4 例⼦4
如果在Generator函数⾥⾯yield⼀个Promise对象。同样不会有任何特殊的地⽅,Promise对象会被yield返回,并且输出"value: Promise { }"
例⼦代码:
1 function pro() {
2  return new Promise((resolve) => {
3    console.log('');
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.uctor);
22 console.log('start!');
23 console.());
24 console.());
25 console.());
26 console.());
27 console.());
输出:
[Sherlock@Holmes Moriarty]$ node app.js
undefined
start!
step 1
{ value: 'str 1', done: false }
step 2
<
{ 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秒。
1.2.5 co库
上⾯四个例⼦⼤概展⽰了es6的Generator和Iterator语法的特性。
类似于提供了我们⼀个状态机的⽀持。
但这⾥有两个问题:
1. 在例⼦4中yield ⼀个Promise对象,并不会有什么特殊现象。不会等待Promise对象被settle之后才继续往下。
2. generator函数返回的只是⼀个Iterator对象,我们不得不⼿动调⽤next()⽅法去进⼊下⼀个状态。
当⽤co库时:
1. co(function*() {}),这⾥⾯的Generator是会⾃动依次next下去,直到结束。
2. yield ⼀个Promise对象时,等到被settle之后才会继续。也正是因为co的这个实现,得以让我们写出“同步形式”⽽“异步本质”的代码。
3. co激发的Generator函数⾥⾯,对yield返回的东西有特殊要求,⽐如不能是String、undefined这些。⽽这些在正常es6语法下是允许的。
例⼦5:
2 const co = require('co');      // 4.6.0版本
3 function pro() {
4  return new Promise((resolve) => {
5    console.log('');
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
<
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.
co 中的yield Iterator对象
在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源码流程。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 || !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = (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.3 es7 —— async函数与await语句
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.
例⼦:
2 function async1() {
3  return new Promise((resolve) => {
4    console.log('');
5    setTimeout(() => {
es6新特性面试
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
<
timeout
resolve value
此外,async函数被执⾏时同普通函数⼀样,⾃动往下执⾏。⽽不像Generator函数需要⼀个Iterator对象来激发。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。