[转] Understanding JavaScript’s async await
PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果
Promise<Action>这样的对象呢,异步调用后的结果是一个Action,传到处理函数里
async/await的作用是,不需要写then函数了,相当于与自动帮你写,你只需要把异步调用后的结果保存下来就可以了
https://ponyfoo.com/articles/understanding-javascript-async-await
http://liubin.org/promises-book/#what-is-promise
The async
/ await
feature didn’t make the cut for ES2016, but that doesn’t mean it won’t be coming to JavaScript. At the time of this writing, it’s a Stage 3 proposal, and actively being worked on. The feature is already in Edge, and should it land in another browser it’ll reach Stage 4 – paving its way for inclusion in the next Edition of the language (see also: TC39 Process).
We’ve heard about this feature for a while, but let’s drill down into it and see how it works. To be able to grasp the contents of this article, you’ll need a solid understanding of promises and generators. These resources should help you out.
- ES6 Overview in 350 Bullet Points
- ES6 Promises in Depth
- ES6 Generators in Depth
- Asynchronous I/O with Generators & Promises
- Promisees Visualization Tool
Using Promises
Let’s suppose we had code like the following. Here I’m wrapping an HTTP request in a Promise
. The promise fulfills with the body
when successful, and is rejected with an err
reason otherwise. It pulls the HTML for a random article from this blog every time.
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);
});
});
}
Typical usage of the promised code shown above is below. There, we build a promise chain transforming the HTML page into Markdown of a subset of its DOM, and then into Terminal-friendly output, to finally print it using console.log
. Always remember to add .catch
handlers to your promises.
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));
}
When ran, that snippet of code produces output as shown in the following screenshot.
That code was “better than using callbacks”, when it comes to how sequential it feels to read the code.
Using Generators
We’ve already explored generators as a way of making the html
available in a synthetic “synchronous” manner in the past. Even though the code is now somewhat synchronous, there’s quite a bit of wrapping involved, and generators may not be the most straightforward way of accomplishing the results that we want, so we might end up sticking to Promises anyways.
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);
});
Keep in mind you should wrap the
yield
call in atry
/catch
block to preserve the error handling we had added when using promises.
Needless to say, using generators like this doesn’t scale well. Besides involving an unintuitive syntax into the mix, your iterator code will be highly coupled to the generator function that’s being consumed. That means you’ll have to change it often as you add new await
expressions to the generator. A better alternative is to use the upcoming Async Function.
Using async
/ await
When Async Functions finally hit the road, we’ll be able to take our Promise
-based implementation and have it take advantage of the synchronous-looking generator style. Another benefit in this approach is that you won’t have to change getRandomPonyFooArticle
at all, as long as it returns a promise, it can be awaited.
Note that await
may only be used in functions marked with the async
keyword. It works similarly to generators, suspending execution in your context until the promise settles. If the awaited expression isn’t a promise, its casted into a 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);
}
Again, – and just like with generators – keep in mind that you should wrap
await
intry
/catch
so that you can capture and handle errors in awaited promises from within theasync
function.
Furthermore, an Async Function always returns a Promise
. That promise is rejected in the case of uncaught exceptions, and it’s otherwise resolved to the return value of the async
function. This enables us to invoke an async
function and mix that with regular promise-based continuation as well. The following example shows how the two may be combined (see Babel REPL).
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'
Going back to the previous example, that’d mean we could return txt
from our async read
function, and allow consumers to do continuation using promises or yet another Async Function. That way, your read
function becomes only concerned with pulling terminal-readable Markdown from a random article on Pony Foo.
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;
}
Then, you could further await read()
in another Async Function.
async function write () {
var txt = await read();
console.log(txt);
}
Or you could just use promises for further continuation.
read().then(txt => console.log(txt));
Fork in the Road
In asynchronous code flows, it is commonplace to execute two or more tasks concurrently. While Async Functions make it easier to write asynchronous code, they also lend themselves to code that is serial. That is to say: code that executes one operation at a time. A function with multiple await
expressions in it will be suspended once at a time on each await
expression until that Promise
is settled, before unsuspending execution and moving onto the next await
expression – not unlike the case we observe with generators and yield
.
To work around that you can use Promise.all
to create a single promise that you can await
on. Of course, the biggest problem is getting in the habit of using Promise.all
instead of leaving everything to run in a series, as it’ll otherwise make a dent in your code’s performance.
The following example shows how you could await
on three different promises that could be resolved concurrently. Given that await
suspends your async
function and the await Promise.all
expression ultimately resolves into a results
array, we can use destructuring to pull individual results out of that array.
async function concurrent () {
var [r1, r2, r3] = await Promise.all([p1, p2, p3]);
}
At some point, there was an await*
alternative to the piece of code above, where you didn’t have to wrap your promises with Promise.all
. Babel 5 still supports it, but it was dropped from the spec (and from Babel 6) – because reasons.
async function concurrent () {
var [r1, r2, r3] = await* [p1, p2, p3];
}
You could still do something like all = Promise.all.bind(Promise)
to obtain a terse alternative to using Promise.all
. An upside of this is that you could do the same for Promise.race
, which didn’t have an equivalent to await*
.
const all = Promise.all.bind(Promise);
async function concurrent () {
var [r1, r2, r3] = await all([p1, p2, p3]);
}
Error Handling
Note that errors are swallowed "silently" within an async
function – just like inside normal Promises. Unless we add try
/ catch
blocks around await
expressions, uncaught exceptions – regardless of whether they were raised in the body of your async
function or while its suspended during await
– will reject the promise returned by the async
function.
Naturally, this can be seen as a strength: you’re able to leverage try
/ catch
conventions, something you were unable to do with callbacks – and somewhat able to with Promises. In this sense, Async Functions are akin to generators, where you’re also able to leverage try
/ catch
thanks to function execution suspension turning asynchronous flows into synchronous code.
Furthermore, you’re able to catch these exceptions from outside the async
function, simply by adding a .catch
clause to the promise they return. While this is a flexible way of combining the try
/ catch
error handling flavor with .catch
clauses in Promises, it can also lead to confusion and ultimately cause to errors going unhandled.
read()
.then(txt => console.log(txt))
.catch(reason => console.error(reason));
We need to be careful and educate ourselves as to the different ways in which we can notice exceptions and then handle, log, or prevent them.
Using async
/ await
Today
One way of using Async Functions in your code today is through Babel. This involves a series of modules, but you could always come up with a module that wraps all of these in a single one if you prefer that. I included npm-run
as a helpful way of keeping everything in locally installed packages.
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
The following command will compile example.js
through browserify
while using babelify
to enable support for Async Functions. You can then pipe the script to node
or save it to disk.
npm-run browserify -t babelify example.js | node
Further Reading
The specification draft for Async Functions is surprisingly short, and should make up for an interesting read if you’re keen on learning more about this upcoming feature.
I’ve pasted a piece of code below that’s meant to help you understand how async
functions will work internally. Even though we can’t polyfill new keywords, its helpful in terms of understanding what goes on behind the curtains of async
/ await
.
Namely, it should be useful to learn that Async Functions internally leverage both generators and promises.
First off, then, the following bit shows how an async function
declaration could be dumbed down into a regular function
that returns the result of feeding spawn
with a generator function – where we’ll consider await
as the syntactic equivalent for yield
.
async function example (a, b, c) {
example function body
}
function example (a, b, c) {
return spawn(function* () {
example function body
}, this);
}
In spawn
, a promise is wrapped around code that will step through the generator function – made out of user code – in series, forwarding values to your “generator” code (the async
function’s body). In this sense, we can observe that Async Functions really are syntactic sugar on top of generators and promises, which makes it important that you understand how each of these things work in order to get a better understanding into how you can mix, match, and combine these different flavors of asynchronous code flows together.
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) {
// finished with failure, reject the promise
reject(e);
return;
}
if (next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
});
}
The highlighted bits of code should aid you in understanding how the
async
/await
algorithm iterates over the generator sequence (ofawait
expressions), wrapping each item in the sequence in a promise and then chaining that with the next step in the sequence. When the sequence is over or one of the promises is rejected, the promise returned by the underlying generator function is settled.
Special thanks to @ljharb, @jaydson, @calvinf, @ericclemmons, @sherman3ero, @matthewmolnar3, and @rauschma for reviewing drafts of this article.
[转] Understanding JavaScript’s async await的更多相关文章
- 【转】6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)
原文:https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec105 ...
- [转] 理解 JavaScript 的 async/await
[From] https://segmentfault.com/a/1190000007535316 边城 2016年11月19日发布 随着 Node 7 的发布,越来越多的人开始研究据说是 ...
- 【前端_js】理解 JavaScript 的 async/await
async 和 await 在干什么 任意一个名称都是有意义的,先从字面意思来理解.async 是“异步”的简写,而 await 可以认为是 async wait 的简写.所以应该很好理解 async ...
- 理解 JavaScript 的 async/await
随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await.我第一次看到这组关键字并不是在 JavaScript 语言里,而是在 c# 5.0 的语法中.C# ...
- JavaScript 的 Async\/Await 完胜 Promise 的六
参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: A ...
- 深入理解理解 JavaScript 的 async/await
原文地址:https://segmentfault.com/a/1190000007535316,首先感谢原文作者对该知识的总结与分享.本文是在自己理解的基础上略作修改所写,主要为了加深对该知识点的理 ...
- JavaScript 的 async/await
随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await. 异步编程的最高境界,就是根本不用关心它是不是异步. async 函数就是隧道尽头的亮光,很多人认 ...
- JavaScript 利用 async await 实现 sleep 效果
const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); ...
- Async/Await替代Promise的6个理由
译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止 ...
随机推荐
- 获取本机的ip
https://4sysops.com/archives/ipv6-tutorial-part-6-site-local-addresses-and-link-local-addresses/ In ...
- trash目录: ~/.local/share/Trash
trash目录:~/.local/share/Trash
- epub3 in action: epub3文件格式简介
epub3文件就是一个符合epub3标准,以epub为扩展名的zip压缩文件.epub3标准则是基于html5.css3.svg等web标准以及mathML等来展示内容.下图就是一个简单epub3文件 ...
- Apache搭建多个站点
如何用Apache搭建的网站系统上运行多个站点呢?最平常的大概有3种方法. 第一种:单IP不同端口 第二种:多IP同端口(独立IP的虚拟空间) 第三种:域名绑定根目录的方式(共享IP的虚拟空间) 下面 ...
- 从零开始学习jQuery (十) jQueryUI常用功能实战
一.摘要 本系列文章将带您进入jQuery的精彩世界, 其中有很多作者具体的使用经验和解决方案, 即使你会使用jQuery也能在阅读中发现些许秘籍. 本文是实战篇. 使用jQueryUI完成制作网站 ...
- VC一些经验系列: 《分享泄漏检测工具:内存、DC、GDI、Handle... 》
分享下自己工作中用到的一些用于泄漏检测的工具 后面的是DC的一些定义和注意事项.(不喜勿看) //=================================================== ...
- MySql定位执行效率较低的SQL语句
MySQL能够记录执行时间超过参数 long_query_time 设置值的SQL语句,默认是不记录的. 获得初始锁定的时间不算作执行时间.mysqld在SQL执行完和所有的锁都被释放后才写入日志.且 ...
- [Stephen]转载 如何提高测试用例评审效率
年前启动的项目,之前一直各种需求文档的评审,之后搁置了一段时间进行其他项目的测试,如今开始各个模块的测试点评审,项目较大,模块较多,仅需求点将近250个.由8个人负责分工完成,几乎每天一个会议对各模块 ...
- base64dll
继上次的dll学习后,想开发个软件,连接到百度的云存储服务器,上传文件.发现要算秘钥,在网上找了到了hmac-sha1,base64的源码,发现有些是c++写的,有些是c写的一起写到一个文件里有些麻烦 ...
- 简单实例一步一步帮你搞清楚MVC3中的路由以及区域
我们都知道MVC 3 程序的所有请求都是先经过路由解析然后分配到特定的Controller 以及 Action 中的,为什么这些知识讲完了Controller Action Model 后再讲呢?这个 ...