异步是 JavaScript 的一大难点之一, 因此 js 的异步解决方案也是多种多样的. 从最早的回调函数, 到 Promise 对象, 到 Generator 函数, 再到现在的 async/await. 很多人都认为 async/await 是异步操作的终极解决方案, 今天就来简单介绍下它.
async/await 这名字取得就很语义化, async 声明一个异步 function,await 用于等待异步 function 执行完成. 并且语法规定, await 只能出现在 async 函数中.
aysnc
async 到底做了什么?
我们看一下下面的代码
- async function test() {return 'hello world'}
- console.log(test())
我们来看看打印出了什么
Promise { 'hello world' }
没错, 它返回了一个 Promise 对象. 从文档中我们知道 await 用于等待一个 Promise 对象. 但是在上面的例子中直接返回了一个字符串, 它不是 Promise 对象, async 函数会通过 Promise.resolve() 把它封装成一个 Promise 对象.
我们回顾一下 Promise 的特点 -- 无等待. 如果这个 async 函数没有 await 的话, 它会立即执行返回一个 Promise 对象, 不会阻塞后面代码的运行.
await
我们知道 await 是在等待一个 async 函数完成, 而 async 函数会返回一个 Promise 对象, 因此 await 表达式的运算结果就是这个 Promise 对象 resolve() 的值.
继续看个例子:
- function test1() {
- return 'test'
- }
- function testAsync(n) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve(n)
- }, 1000)
- })
- }
- async function test() {
- const result1 = await test1()
- console.log(result1)
- const result2 = await testAsync(999)
- console.log(result2)
- }
- test()
- console.log('同步代码')
上述代码首先打出了 "同步代码", 随后又立即打出 "test", 并在一秒后打出了 "999". 我们可以得出以下结论:
await 会阻塞它所在的异步函数的后面代码的执行 (因为它需要等到 testAsync 函数的结果才会往下执行), 但是不会阻塞异步函数之外的代码的执行 (因为先打印的 "同步代码"), 其实它内部的阻塞都被封装到一个 Promise 对象中异步执行, 我想可能是因为这样 await 才必须在 async 函数中吧.
如果 await 等到的不是 Promise 对象的话会将它转成立即 resolve 的 Promise 对象, 并将该值作为 await 表达式的结果 (打印完 "同步代码" 之后立即打出 "test"); 如果是的话就等着 Promise 对象 resolve(), 然后将 resolve 的结果作为 await 表达式的结果 (一秒后打印 "999").
在 async 函数中, 这样的代码看起来就像是同步代码, await 等待到结果才执行后面的语句, 但是却不会阻塞 async 函数之外的代码的执行.
async/await
刚开始可能会感觉 async/await 就是将 Promise 中的 then 变成了用 await 的方式书写, 好像并没有什么优势.
我们先举个栗子: 假设一个功能需要多个步骤完成, 并且每个步骤都是异步的, 而且后面的步骤依赖于前面步骤的结果
- function asycnTest(time) {
- return new Promise(resolve => {
- setTimeout(() => {
- resolve(time + 500)
- }, time)
- })
- }
- function step1(time) {
- console.log(`step1 用时 ${time}`)
- return asycnTest(time)
- }
- function step2(time) {
- console.log(`step2 用时 ${time}`)
- return asycnTest(time)
- }
- function step3(time) {
- console.log(`step3 用时 ${time}`)
- return asycnTest(time)
- }
先看看用 Promise 要怎么写:
- function promiseDoIt() {
- console.time('promiseDoIt')
- step1(1000)
- .then(time2 => step2(time2))
- .then(time3 => step3(time3))
- .then(result => {
- console.log(` 结果是 ${result}`)
- console.timeEnd('promiseDoIt')
- })
- }
- promiseDoIt()
- // step1 用时 1000
- // step2 用时 1500
- // step3 用时 2000
- // 结果是 2500
- // promiseDoIt: 4507.135ms
上述三个步骤一共用时 1000+1500+2000 共 4500ms, 和 console.time()/console.timeEnd() 运算结果一致. 如果用 async/await 来写会怎么样呢?
- async function asyncDoIt() {
- console.time('asyncDoIt')
- const time2 = await step1(1000)
- const time3 = await step2(time2)
- const result = await step3(time3)
- console.log(` 结果是 ${result}`)
- console.timeEnd('asyncDoIt')
- }
- asyncDoIt()
- // step1 用时 1000
- // step2 用时 1500
- // step3 用时 2000
- // 结果是 2500
- // asyncDoIt: 4504.294ms
运行结果都是一样的, 但是这看起来就像同步代码, 非常清晰. 再改一下需求, 如果后面步骤依赖前面每一个步骤的结果, 又该怎么改呢? 反正用 async/await 修改是很方便的, 只需要将前面步骤的结果当参数传递进去就行了, 至于 Promise 的写法, 好像就有点麻烦了...
另外, Promise 对象不会一直是 resolve 啊, 也会 reject 的, 所以需要将 await 放在 try...catch 代码块中对错误进行处理
- async function asyncTest() {
- try {
- await Promise.reject('出错了')
- } catch (err) {
- console.log(err);
- }
- }
- // 或者直接使用 Promise 的 catch
- async function asyncTest() {
- await Promise.reject('出错了').catch(err => {
- console.log(err)
- });
- }
这样才不会阻塞代码的正常运行
今天就先到这吧, 以后有了新的理解再补充
查看原文 http://blog.linxunzyf.cn/posts/ec020e93/
来源: https://juejin.im/post/5acde16a6fb9a028b86e60e8