上篇:es5、es6、es7中的异步写法
本作品采用知识共享署名 4.0 国际许可协议进行许可。转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/es-async
本博客同步在http://www.cnblogs.com/papertree/p/7152462.html
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);
.....
Promise.resolve()
.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步骤。
iter.next()返回的是一个含有value、done属性的对象,done表示是否到达结尾,value表示yield的返回值。
这里需要注意的是,Generator函数调用时返回一个Iterator,但是本身的代码是停止的,等iter.next()才会开始执行。
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());
输出:
[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.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是一个{}。
同样的,把第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).
(来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*)
1.2.4 例子4
如果在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秒。
1.2.5 co库
上面四个例子大概展示了es6的Generator和Iterator语法的特性。
类似于提供了我们一个状态机的支持。
但这里有两个问题:
- 在例子4中yield 一个Promise对象,并不会有什么特殊现象。不会等待Promise对象被settle之后才继续往下。
- generator函数返回的只是一个Iterator对象,我们不得不手动调用next()方法去进入下一个状态。
当用co库时:
- co(function*() {}),这里面的Generator是会自动依次next下去,直到结束。
- yield 一个Promise对象时,等到被settle之后才会继续。也正是因为co的这个实现,得以让我们写出“同步形式”而“异步本质”的代码。
- 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('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.
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 || 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.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.
(来自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对象来激发。
上篇:es5、es6、es7中的异步写法的更多相关文章
- ES7中前端异步特性:async、await。
在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是"异步"的意思,async用于声明一个函数是异步的 ...
- 【转】React Native中ES5 ES6写法对照
很多React Native的初学者都被ES6的问题迷惑:各路大神都建议我们直接学习ES6的语法(class Foo extends React.Component),然而网上搜到的很多教程和例子都是 ...
- React/React Native的 ES5 ES6 写法对照
ES5 ES6 模块 var React = require("react-native); var { Image, Text, View } = React; import Re ...
- es6,es7,es8
概述 ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言.目前JavaScript使用的ECMAScript版本为ECMAScript-262. ECMAScript 标 ...
- es6/es7/es8常用新特性总结(超实用)
本文标题有误导性,因为我其实想写node8的新特性,说实话一下子从node v1.x跳跃到node 8.x+ 真有点受宠若惊的感觉.一直觉得node 数组. 对象.序列等的处理没有python方便,因 ...
- 简述ES5 ES6
很久前的某一天,一位大神问我,你知道ES6相对于ES5有什么改进吗? 我一脸懵逼的反问,那个啥,啥是ES5.ES6啊. 不得不承认与大神之间的差距,回来深思了这个问题,结合以前的知识,算是有了点眉目. ...
- es5~es6
1.用箭头函数减少代码(相信你在Vue已经看到了) ES5: function greetings (name) { return 'hello ' + name } ES6: const greet ...
- node mysql es6/es7改造
本文js代码采取了ES6/ES7的写法,而不是commonJs的写法.支持一波JS的新语法.node版本的mysql驱动,通过npm i mysql安装.官网地址:https://github.com ...
- 【JS】336- 拆解 JavaScript 中的异步模式
点击上方"前端自习课"关注,学习起来~ JavaScript 中有很多种异步编程的方式.callback.promise.generator.async await 甚至 RxJS ...
随机推荐
- Python语言程序设计之三--列表List常见操作和错误总结
最近在学习列表,在这里卡住了很久,主要是课后习题太多,而且难度也不小.像我看的这本<Python语言程序设计>--梁勇著,列表和多维列表两章课后习题就有93道之多.我的天!但是题目出的非常 ...
- Hive 执行查询语句报错,由于内存空间不足导致
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.hdfs.server.namenode.SafeModeException): Can ...
- windons杀死8080进程
1,netstat -aon|findstr "8080" //8080端口号 2,taskkill -PID 2976 -F //2976 ,8080端口号对应的进程号
- 大数据学习——sql练习
1. 现有如下的建表语句和数据: 建表语句 create table student(Sno int,Sname string,Sex string,Sage int,Sdept string)row ...
- Shell脚本学习指南 [ 第三、四章 ] 查找与替换、文本处理工具
摘要:第三章讨论的是编写Shell脚本时经常用到的两个基本操作.第四章总共介绍了约30种处理文本文件的好用工具. 第三章 查找与替换 概括:本章讨论的是编写Shell脚本时经常用到的两个基本操作:文本 ...
- BZOJ 4650 [Noi2016]优秀的拆分 ——后缀数组
我们只需要统计在某一个点开始的形如$AA$字符串个数,和结束的个数相乘求和. 首先枚举循环节的长度L.即$\mid (A) \mid=L$ 然后肯定会经过s[i]和[i+L]至少两个点. 然后我们可以 ...
- 【扩展kmp+最小循环节】HDU 4333 Revolving Digits
http://acm.hdu.edu.cn/showproblem.php?pid=4333 [题意] 给定一个数字<=10^100000,每次将该数的第一位放到放到最后一位,求所有组成的不同的 ...
- C++ string 类中的 assign()函数
C++ string 类的成员函数,用于拷贝.赋值操作,它们允许我们顺次地把一个 string 对象的部分内容拷贝到另一个 string 对象上. 函数原型 string &operator= ...
- java中执行JS脚本
package 测试包; import javax.script.*; public class SSSSSSSSS { public SSSSSSSSS() { // TODO Auto-gener ...
- [MFC] TabControl选项卡的使用
MFC中,因项目需要使用TabControl ,使用过程中发现,MFC中的TabControl与C#的TabControl不同,不能通过属性来创建选项页,只能代码生成绑定. 以下为具体的实现方法步骤: ...