目前async / await
特性并无被添加到ES2016标准中,但不表明这些特性未来不会被加入到Javascript中。在我写这篇文章时,它已经到达第三版草案,而且正迅速的发展中。这些特性已经被IE Edge支持了,并且它将会到达第四版,届时该特性将会登录其余浏览器 -- 为加入该语言的下一版本而铺路(也能够看看:TC39进程)。javascript
咱们据说特性已经有一段时间了,如今让咱们深刻它,并了解它是如何工做的。为了可以了解这篇文章的内容,你须要对promise和生成器对象有深厚的理解。这些资源或许能够帮到你。php
让咱们假设咱们有像下面这样的代码。在这里我将一个HTTP请求包装在一个Promise
对象中。这个Promise在成功时会返回body
对象,被拒绝时会将缘由err
返回。它每次都会在本博客(原做者博客)中为一篇随机文章拉取html内容。html
var request = require('request'); function getRandomPonyFooArticle () { return new Promise((resolve, reject) => { request('https://ponyfoo.com/articles/random', (err, res, body) => { if (err) { reject(err); return; } resolve(body); }); }); }
上述的promise代码的典型用法是像下面写的这样。 在那里,咱们新建了一个promise链来将HTML页面中的DOM对象的一个子集转换成Markdown,而后再转换成对终端友好的输出, 最终再使用console.log
输出它。 永远要记得为你的promise添加.catch
处理器。java
var hget = require('hget'); var marked = require('marked'); var Term = require('marked-terminal'); printRandomArticle(); function printRandomArticle () { getRandomPonyFooArticle() .then(html => hget(html, { markdown: true, root: 'main', ignore: '.at-subscribe,.mm-comments,.de-sidebar' })) .then(md => marked(md, { renderer: new Term() })) .then(txt => console.log(txt)) .catch(reason => console.error(reason)); }
当代码运行后,这段代码将产生像如下截图所示的输出。node
上面那段代码就是“比用回调函数更好”的写法,它能让你感受像在按顺序的阅读代码。react
过去,经过探索,咱们发现生成器能够用一种“同步”合成的方法来得到html
。即便如今的代码有一些同步写法,其中仍是涉及至关多的包装,并且生成器可能不是最直截了当的达到咱们指望结果的方法,最终可能不管如何咱们都会坚持改成使用promise。git
function getRandomPonyFooArticle (gen) { var g = gen(); request('https://ponyfoo.com/articles/random', (err, res, body) => { if (err) { g.throw(err); return; } g.next(body); }); } getRandomPonyFooArticle(function* printRandomArticle () { var html = yield; var md = hget(html, { markdown: true, root: 'main', ignore: '.at-subscribe,.mm-comments,.de-sidebar' }); var txt = marked(md, { renderer: new Term() }); console.log(txt); });
“请记住,在使用promise时,你应该将yield调用包装在try/catch块中来保留咱们添加的错误处理器”
es6
不说你也知道,像这样使用生成器并不容易扩展。除了涉及直观的语法的混入,你的迭代代码会高度耦合到生成器函数中,这将会下降扩展性。这表示你在添加新的await表达式到生成器中时须要常常修改它。一个更好的替代方案是使用即将到来的Async函数。github
当Async函数终于落地时,咱们将能够采起基于Promise的实现方法并使用它的优势,即像写同步生成器同样写异步。这种作法的另外一个好处是你彻底不须要再去修改getRandomPonyFooArticle
方法,在它返回一个承诺前,它会一直等待。shell
要注意的是,await只能在函数中用async
关键字标记后才能使用 它的工做方式和生成器很类似,直到promise完成以前,会在你的上下文中暂停处理。若是等待表达式不是一个promise,它也会被改形成一个promise。
read();
async function read () { var html = await getRandomPonyFooArticle(); var md = hget(html, { markdown: true, root: 'main', ignore: '.at-subscribe,.mm-comments,.de-sidebar' }); var txt = marked(md, { renderer: new Term() }); console.log(txt); }
“再次, -- 跟生成器同样 -- 记住,你最好把`await`包装到`try/catch`中,这样你就能够在异步函数中对返回后的promise进行错误捕获和处理。”
此外,一个Async函数老是会返回一个Promise
对象。 这个promise在出现没法捕获的异常时会被拒绝,不然它会处理async
函数的返回值。这就容许咱们调用一个async
函数并混入常规的基于promise的扩展。如下例子展现了两个方法的结合(看看Babel的交互式解释器)。
async function asyncFun () { var value = await Promise .resolve(1) .then(x => x * 3) .then(x => x + 5) .then(x => x / 2); return value; } asyncFun().then(x => console.log(`x: ${x}`)); // <- 'x: 4'
回到前一个例子中,那表示咱们能够从异步读取
函数中返回文本
,而且容许调用者使用promise或另外一个Async函数进行扩展。 那样,你的读取
函数将只需关注从Pony Foo上的随机文章中拉取终端可读的Markdown便可。
async function read () { var html = await getRandomPonyFooArticle(); var md = hget(html, { markdown: true, root: 'main', ignore: '.at-subscribe,.mm-comments,.de-sidebar' }); var txt = marked(md, { renderer: new Term() }); return txt; }
而后,你能够进一步在另外一个Async函数中调用await read()
。
async function write () { var txt = await read(); console.log(txt); }
或者你能够只使用promise对象来进一步扩展。
read().then(txt => console.log(txt));
在异步代码流中,老是能遇到同时执行两个或更多任务的状况。当Async函数更容易编写异步代码后,它们也将本身依次传递给代码。 这就是说:代码在一个时刻只执行一个操做。一个包含多个await
表达式的函数在promise对象执行完以前,在恢复执行和移动到下一个await
表达式以前,会在每一个await
表达式处暂停一次, -- 就跟咱们在生成器和yield
关键字处观察到的状况同样。
你可使用Promise.all
来解决建立单个promise对象并进行等待的功能。 固然,最大的问题是从习惯于让全部东西都串行运行改为使用Promise.all
, 不然这将给你的代码带来性能瓶颈。
下面的例子展现了你如何同时完成对三个不一样的promise对象进行等待
操做。特定的await
操做符会暂停你的Async
函数,和等待 Promise.all
表达式一块儿,最终会被解析到一个结果
数组中,咱们可使用析构函数逐个拉取该数组中的单个结果。
async function concurrent () { var [r1, r2, r3] = await Promise.all([p1, p2, p3]); }
在某些状况下, 能够用 await *
来改动上述代码片断,让你没必要用Promise.all来包装你的promise对象。Babel 5依然支持这种特性,但它已经从规格说明中移除(也已经从Babel 6中移除) -- 由于这些缘由
async function concurrent () { var [r1, r2, r3] = await* [p1, p2, p3]; }
你依然能够用相似all = Promise.all.bind(Promise)
的代码来作些事情,来得到一个简洁的替代Promise.all
的方法。在这之上的是,你能够对Promise.race
作相同的事情,而这跟使用await*
并不等价。
const all = Promise.all.bind(Promise); async function concurrent () { var [r1, r2, r3] = `await all([p1, p2, p3])`; }
要注意的是,在异步函数中,错误会被“默默的”吞噬 -- 就像在普通的Promise对象中同样。 除非咱们围绕await
表达式添加try/catch
块 -- 而无论在暂停
时,它们会在你的异步
函数体中发生仍是在它暂停时发生 -- promise对象会被拒绝并经过Async
函数返回错误。
天然,这能够看做是一个能力: 你能够利用try/catch
代码块,有些东西你没法用回调函数实现-- 但能够用Promise对象实现。 在这种状况下,Async函数就相似生成器
,得益于函数执行暂停特性,你能够利用try/catch
将异步流代码写成同步代码的样子。
此外, 你能够在Async
函数外捕获这些异常, 只须要简单的对它们返回的promise对象添加.catch()
方法调用。在promise对象中尝试用.catch
方法来将try/catch
错误处理组合起来是一种比较灵活的方法,但该方法也可能致使混乱并最终致使错误没法处理。
read()
.then(txt => console.log(txt)) .catch(reason => console.error(reason));
咱们要当心谨慎并时刻提醒本身用不一样的方法来让咱们能够发现错误、处理错误或预防错误。
async/await
现在,有一种在你的代码中使用Async函数的方法是经过Babel。这涉及一系列模块,但只要你愿意,你老是能够拿出一个模块来将所有这些代码包装进去。我包含npm-run
做为一个有用的方法,用于保持本地的全部东西都用包进行安装。
npm i -g npm-run
npm i -D \ browserify \ babelify \ babel-preset-es2015 \ babel-preset-stage-3 \ babel-runtime \ babel-plugin-transform-runtime echo '{ "presets": ["es2015", "stage-3"], "plugins": ["transform-runtime"] }' > .babelrc
在使用babelify
为Async函数提供支持时,如下命令会将example.js
经过browserify
进行编译。而后你就能够用管道将脚本传输给node
执行,或将脚本保存到硬盘中。
npm-run browserify -t babelify example.js | node
Async函数规格草案出奇的短,而且应该能成为一个有趣的读物, 若是你热衷于学习更多这些即将到来的功能。
我已经粘贴了一段代码在下面, 它是为了帮助你理解async
函数的内部是如何工做的。即便咱们不能够填充新的关键字,它也能够帮助你理解在async/await
的帷幕后面发生了什么事情。
“换句话说,它应该对学习异步函数内部原理很是有帮助,不管是对生成器仍是promise。”
首先,下面的一小段代码展现了一个async函数
如何经过常规的function
关键字来简化声明过程,这将返回一个生成spawn
生成器函数的结果 -- 咱们会认为await
在语法上是和yield
等价的。
async function example (a, b, c) { example function body } function example (a, b, c) { return spawn(function* () { example function body }, this); }
在spawn
中,promise会被代码包装起来并传入生成器函数中,经过用户代码串行的执行,并将值传递到你的“生成器”代码中(async
函数的函数体中)。 在这个意义上,咱们能够注意Async函数真的是生成器和primose对象之上的语法糖,这对于让你理解其中每个环节是如何工做来讲很是重要,这是为了让你对于混合、匹配、合并不一样的异步代码流的写法有一个更好的理解。
function spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self); step(() => gen.next(undefined)); function step (nextF) { var next; try { next = nextF(); } catch(e) { // 执行失败,并拒绝promise对象 reject(e); return; } if (next.done) { // 执行成功,处理promise对象 resolve(next.value); return; } // 未完成,以yield标记的promise对象呗中断,并在此执行step方法 Promise.resolve(next.value).then( v => step(() => gen.next(v)), e => step(() => gen.throw(e)) ); } }); }