async / await 使用注意

19 年 5 月 28 日 星期二 (已编辑)
1005 字
6 分钟

js 中的 async / await 有何作用?

有一种特殊的语法可以更舒适地与Promise一起使用,称为“async/await”。它非常容易理解和使用。

写过JQuery的同学对于其中的回调肯定印象深刻,在多层嵌套回调中,代码结构简直乱如麻。众多饱受其荼毒的受害者对此起了一个形象的名称:回调地狱。

为了解决此类问题,JS提供了两个新语法:Promiseasync / await,其中 async / await 一般配套使用。

async / await 语法

javascript
async function name([param[, param[, ...param]]]) {
   statements
}

参数

  • name 函数名
  • param 形参
  • statements 函数主体语句,其中 await 为可选使用

返回值

返回一个Promise,它将由async函数返回的值来解决,或者被async函数抛出或未捕获到的异常拒绝。

示例

javascript
function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: "resolved"
}

asyncCall();

使用注意

async 函数总是返回 Promise

即使返回值只是一个primitive值,async函数也会通过return自动将返回值包装成一个Promise对象返回。 因此,下面两组函数是等价的。

正常 (Fulfill)

javascript
// async函数
async function foo () {
  return 'a'
}

// Promise
function foo () {
  return Promise.resolve('a')
}

异常 (Reject)

javascript
// async函数
async function foo () {
  throw new Error('error')
}

// Promise
function foo () {
  return Promise.reject(new Error('error'))
}

await总是按顺序执行

使用async函数之前,我们还得搞清楚它的运行机制。尤其是在执行顺序上,完全用同步的思维也许并不适用于async函数。

javascript
function asyncGet (x) {
  return new Promise(resolve => setTimeout(() => {
    console.log('a')
    resolve(x)
  }, 500))
}

async function test () {
  console.log('b')
  const x = 3 + 5
  console.log(x)

  const a = await asyncGet(1)
  console.log(a)

  const b = await asyncGet(2)
  console.log(b)

  console.log('c')  
  return a + b
}

const now = Date.now()
console.log('d')
test().then(x => {
  console.log(x)
  console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')
  1. async函数和普通函数一样按顺序执行,同时,在执行到await语句时,返回一个Promise对象
  2. await可以理解为将async函数挂起,直到等待的Promisefulfill或者reject,再继续执行之后的代码
  3. async函数的返回值和普通Promise没有区别

因此,上面代码输出应该是

text
d
b
8
f
a
1
a
2
c
3
elapsed: 1010

注意 d 和 f 中间的输出

让我们再来看一个混合了Promise的版本。

javascript
function asyncGet (x) {
  return new Promise(resolve => setTimeout(() => {
    console.log('a')
    resolve(x)
  }, 500))
}

async function test () {
  console.log('b')
  const x = 3 + 5
  console.log(x)

  const [a, b] = await Promise.all([
    asyncGet(1),
    asyncGet(2)
  ])

  console.log('c')  
  return a + b
}

const now = Date.now()
console.log('d')
test().then(x => {
  console.log(x)
  console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')

输出结果

text
d
b
8
f
a
a
c
3
elapsed: 509

注意到elapsed的差别了吗?这就是为什么我们说await总是顺序执行的。不同的await之间无法并行执行,想要真正的完全异步还得借助类似Promise.all这样的方法。

async 函数和 callback

await 只能能影响直接包裹它的 async 函数。因此在 callback 函数中的 await 并不会挂起整个 async 函数的执行。

一种常见的错误

javascript
async function getAll (vals) {
  return vals.map(v => await asyncGet(v))
}

这段代码有语法错误,await 并不在 async 函数内部。如果给 mapcallback 加上 async 呢?

javascript
async function getAll (vals) {
  return vals.map(async v => await asyncGet(v))
}

这段代码虽然能执行,但还有两个问题。

  1. 返回一个Promise对象的数组,并不是我们期待的value数组
  2. await只会暂停mapcallback,因此map完成时,不能保证asyncGet也全部完成

正确的写法还得借助 Promise.all

javascript
async function getAll (vals) {
  return Promise.all(vals.map(v => asyncGet(v)))
}

总结

从上文我们可以看出,Promiseasync 函数的基础,想要愉快的使用 async 函数,必须对 Promise 有比较深入的理解。甚至一些常见的任务,仅仅依靠 async 函数无法实现。 希望大家看完本文后能对 async 函数有加更全面的认识,这样使用起来才会更加顺手。

文章标题:async / await 使用注意

文章作者:linaaaqi

文章链接:https://linaaaqi.com/posts/async-await-使用注意[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。