async函数是什么?
我们创建了 promise 但不能同步等待它执行完成。我们只能通过 then 传一个回调函数这样很容易再次陷入 promise 的回调地狱。实际上,async/await 在底层转换成了 promise 和 then 回调函数。
每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。async/await 的实现,离不开 Promise。从字面意思来理解,async 是“异步”的简写,而 await 是 async wait 的简写可以认为是等待异步方法执行完成。
async函数作用是什么?
我们创建了 promise 但不能同步等待它执行完成。我们只能通过 then 传一个回调函数这样很容易再次陷入 promise 的回调地狱。async函数优化了promise 的回调问题,被称作是异步的终极解决方案。
async函数内部做了什么?
async 函数会返回一个 Promise 对象,如果在函数中 return 一个直接量(普通变量),async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。如果返回了promise那就以返回的promise为准。
如果async函数没有返回值呢?它会返回 Promise.resolve(undefined)。
1 | async function fn () { |
也可以显式的返回一个promise,这个将会是同样的结果:
1 | async function fn () { |
async确保了函数返回一个promise,即使其中包含非promise。
await关键字?
按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值。
虽然await后面通常是一个异步操作(promise),但是这不代表 await 后面只能跟异步操作,也就是说await后面实际是可以接普通函数调用或者直接量的。
1 | function getSomething() { |
关键词await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。
1 | let promise = new Promise((resolve, reject) => { |
await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?其实await是个运算符,用于组成表达式,awai 表达式的运算结果取决于它等的东西。
- 如果 await 后面跟的不是一个 Promise对象,那 await 后面表达式的运算结果就是它等到的结果;
- 如果 await 后面跟的是一个 Promise 对象,await 它会“阻塞”后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值作为 await 表达式的运算结果。
⚠️ async 函数调用不会造成“阻塞”,它内部所有的“阻塞”都被封装在一个 Promise对象中异步执行。因此这里的阻塞理解成异步等待更合理。这也就是 await必须用在async函数中的原因。
async/await如何使用?
async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。
例如,用 setTimeout 模拟耗时的异步操作,先来看看不用async/await的写法:
1 | function takeLongTime () { |
改用async/await呢?
1 | function takeLongTime () { |
async/await 的优势?
单一的 Promise 链并不能发现 async/await 的优势,但是如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:
1 | /** |
promise来实现三个步骤的处理
1 | function doIt () { |
用async/await来写,结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样:
1 | async function doIt () { |
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。
1 | function step1(n) { |
用async/await来实现:
1 | async function doIt() { |
promise的方式实现呢?
1 | function doIt() { |
错误处理
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。
写法1:
1 | async function myFunction() { |
写法2:
1 | async function myFunction() { |
await 命令只能用在 async 函数之中,如果用在普通函数,就会报错
1 | async function dbFuc(db) { |
但是,如果将 forEach 方法的参数改成 async 函数,也有问题。
1 | function fetch(x) { |
期望的输出结果应该是3 2 1 end,然而实际打印的结果是:end 1 2 3,为什么?
forEach 只支持同步代码。可以参考下 Polyfill 版本的 forEach,简化以后类似就是这样的伪代码:
1 | while (index < arr.length) { |
从上述代码中我们可以发现,forEach 只是简单的执行了下回调函数而已,并不会去处理异步的情况。并且你在 callback 中即使使用 break 也并不能结束遍历。
正确的写法是采用 for…of。
1 | async function test() { |
因为 for…of 内部处理的机制和 forEach 不同,forEach 是直接调用回调函数,for…of 是通过迭代器的方式去遍历。
1 | async function test() { |
以上代码等价于 for…of,可以看成 for…of 是以上代码的语法糖。
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
1 | async function dbFuc(db) { |
async实现原理
async 就等于Generator+自动执行器。
Generator
1 | var x = 1; |
生成器*foo()没有像普通函数一样运行,它只是创建了一个迭代器。
1 | it.next() |
- 第一次调用next(),会从函数开始位置执行到第一个暂停点(yield处),它返回了一个对象,对象的value值就是当前yield的值。此时执行了一次x++;所以x的值为2。
- 再次执行next()会运行到函数结束。
这种方式可以让函数体的代码分段执行,需要手动next()来控制代码的进度。所以用它来处理异步的方式也比较明了。yield暂停函数体代码,当异步操作完成后再使用next()恢复函数的代码。
1 | function readFile (a) { |
执行到readFile的时候暂停了foo函数,直到Promise被解决后调用了next(),才恢复了函数。
自动执行器
可以看到,it.next()返回{ value:x,done:false }。根据done的值是可以知道生成器内部代码是否执行完成。所以写个递归就可以了,注意异步操作返回结果才可以继续执行
1 | function run(g) { |
测试一下:
1 | function readFile(a){ |
- 第一次执行foo().next()打印出a, 然后生成器内函数暂停, 打印d
- 定时器到了后再打出b和c。这个简单的自动执行器,是针对yield后面跟着promise对象的情况。实际使用可能不能这么写,它只是帮助你理解。实战可以选择co模块。
async 等于Generator+自动执行器
1 | async function test(){}; |
模拟实现async/await总结
将 Generator 函数和自动执行器,包装在一个函数里
1 | function spawn(genF) { |
总结
放在一个函数前的async有两个作用:
- 使函数总是返回一个promise
- 允许在这其中使用await
promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:
- 如果它是一个错误,异常就产生了,就像在那个地方调用了throw error一样。
- 否则,它会返回一个结果,我们可以将它分配给一个值
async/await函数实现原理:将 Generator 函数和自动执行器,包装在一个函数里
参考文章:
http://www.ruanyifeng.com/blog/2015/05/async.html
https://juejin.im/post/5a9516885188257a6b061d72
https://javascript.info/async-await
https://segmentfault.com/a/1190000007535316?utm_source=tag-newest
https://juejin.im/post/5cb1d5a3f265da03587bed99?utm_source=gold_browser_extension