async / await 是 ES7 的重要特性之一,也是目前社区里公认的优秀异步解决方案。目前,async / await 这个特性已经是的建议,可以看看,本篇文章将分享 async / await 是如何工作的,阅读本文前,希望你具备 Promise、generator、yield 等 ES6 的相关知识。
在详细介绍 async / await 之前,先回顾下目前在 ES6 中比较好的异步处理办法。下面的例子中数据请求用 Node.js 中的 request 模块,数据接口采用提供的 repo 代码仓库详情 API 作为例子演示。
虽然 Node.js 的异步 IO 带来了对高并发的良好支持,同时也让 "回调" 成为灾难,很容易造成回调地狱。传统的方式比如使用具名函数,虽然可以减少嵌套的层数,让代码看起来比较清晰。但是会造成比较差的编码和调试体验,你需要经常食用 ctrl + f 去寻找某个具名函数的定义,这使得 IDE 窗口经常上下来回跳动。使用 Promise 之后,可以很好的减少嵌套的层数。另外 Promise 的实现采用了状态机,在函数里面可以很好的通过 resolve 和 reject 进行流程控制,你可以按照顺序链式的去执行一系列代码逻辑了。下面是使用 Promise 的一个例子:
- const request = require('request');
- // 请求的url和header
- const options = {
- url: 'https://api.github.com/repos/cpselvis/zhihu-crawler',
- headers: {
- 'User-Agent': 'request'
- }
- };
- // 获取仓库信息
- const getRepoData = () => {
- return new Promise((resolve, reject) => {
- request(options, (err, res, body) => {
- if (err) {
- reject(err);
- }
- resolve(body);
- });
- });
- };
- getRepoData()
- .then((result) => console.log(result);)
- .catch((reason) => console.error(reason););
不过 Promise 仍然存在缺陷,它只是减少了嵌套,并不能完全消除嵌套。举个例子,对于多个 promise 串行执行的情况,第一个 promise 的逻辑执行完之后,我们需要在它的 then 函数里面去执行第二个 promise,这个时候会产生一层嵌套。另外,采用 Promise 的代码看起来依然是异步的,如果写的代码如果能够变成同步该多好啊!
谈到 generator,你应该不会对它感到陌生。在 Node.js 中对于回调的处理,我们经常用的就是使用 generator 结合 promise 来实现的,co 是 coroutine 的简称,借鉴于 python、lua 等语言中的协程。它可以将异步的代码逻辑写成同步的方式,这使得代码的阅读和组织变得更加清晰,也便于调试。
- const co = require('co');
- const request = require('request');
- const options = {
- url: 'https://api.github.com/repos/cpselvis/zhihu-crawler',
- headers: {
- 'User-Agent': 'request'
- }
- };
- // yield后面是一个生成器 generator
- const getRepoData = function * () {
- return new Promise((resolve, reject) = >{
- request(options, (err, res, body) = >{
- if (err) {
- reject(err);
- }
- resolve(body);
- });
- });
- };
- co(function * () {
- const result = yield getRepoData;
- // ... 如果有多个异步流程,可以放在这里,比如
- // const r1 = yield getR1;
- // const r2 = yield getR2;
- // const r3 = yield getR3;
- // 每个yield相当于暂停,执行yield之后会等待它后面的generator返回值之后再执行后面其它的yield逻辑。
- return result;
- }).then(function(value) {
- console.log(value);
- },
- function(err) {
- console.error(err.stack);
- });
虽然 co 是社区里面的优秀异步解决方案,但是并不是语言标准,只是一个过渡方案。ES7 语言层面提供 async / await 去解决语言层面的难题。目前 async / await 在 IE edge 中已经可以直接使用了,但是 chrome 和 Node.js 还没有支持。幸运的是,babel 已经支持 async 的 transform 了,所以我们使用的时候引入 babel 就行。在开始之前我们需要引入以下的 package,preset-stage-3 里就有我们需要的 async/await 的编译文件。
无论是在 Browser 还是 Node.js 端都需要安装下面的包。
- $ npm install babel-core --save
- $ npm install babel-preset-es2015 --save
- $ npm install babel-preset-stage-3 --save
这里推荐使用 babel 官方提供的 require hook 方法。就是通过 require 进来后,接下来的文件进行 require 的时候都会经过 Babel 的处理。因为我们知道 CommonJs 是同步的模块依赖,所以也是可行的方法。这个时候,需要编写两个文件,一个是启动的 js 文件,另外一个是真正执行程序的 js 文件。
启动文件 index.js
- require('babel-core/register');
- require('./async.js');
真正执行程序的 async.js
- const request = require('request');
- const options = {
- url: 'https://api.github.com/repos/cpselvis/zhihu-crawler',
- headers: {
- 'User-Agent': 'request'
- }
- };
- const getRepoData = () = >{
- return new Promise((resolve, reject) = >{
- request(options, (err, res, body) = >{
- if (err) {
- reject(err);
- }
- resolve(body);
- });
- });
- };
- async
- function asyncFun() {
- const value = await getRepoData();
- // ... 和上面的yield类似,如果有多个异步流程,可以放在这里,比如
- // const r1 = await getR1();
- // const r2 = await getR2();
- // const r3 = await getR3();
- // 每个await相当于暂停,执行await之后会等待它后面的函数(不是generator)返回值之后再执行后面其它的await逻辑。
- return value;
- }
- asyncFun().then(x = >console.log(`x: $ {
- x
- }`));
注意点:
其实,async / await 的用法和 co 差不多,await 和 yield 都是表示暂停,外面包裹一层 async 或者 co 来表示里面的代码可以采用同步的方式进行处理。不过 async / await 里面的 await 后面跟着的函数不需要额外处理,co 是需要将它写成一个 generator 的。
来源: