JS笔记《async与await》

92 阅读3分钟

概述

  • async函数就是Generator函数的语法糖,就是将Generator函数的星号(*)替换成async,将yield替换成await,仅此而已。
  • async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);  // 3秒后打印 hello world
}

asyncPrint('hello world', 3000);

返回Promise对象

  • async函数会返回一个Promise对象,函数内部return的返回值会成为then方法回调函数的参数。
async function f() {
  return 'hello world';
}

f().then(v => console.log(v))  // "hello world"
  • async函数内部如果抛出错误,会导致返回的Promise对象变为rejected状态。抛出的错误对象会被catch方法回调函数接收到。
async function f() {
  throw new Error('出错了');
}

f().then(v => console.log('resolve', v))
  .catch(e => console.log('reject', e))  // reject Error: 出错了
  • async返回的Promise对象,必须等到内部所有await命令后面的promise对象执行完才会发生状态改变,除非遇到return或者抛出错误。只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
function timer(str, delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(str)
    }, delay);
  })
}

async function foo() {
  await timer('timer1', 500)
  await timer('timer2', 1500)
  await timer('timer3', 2500)
  return 'timer complete';
}

foo().then(res => {
  console.log(res)   // timer1 timer2 timer3   timer complete
}).catch(err => {
  console.log(err)
})

await命令

  • 正常情况下,await后面是一个Promise对象,返回该对象的结果。如果不是,就直接返回对应的值。
async function foo(){
  return await 1;  // 等同于 return 1
}

foo().then(res => {
  console.log(res)  // 1
})
  • await后面的Promise对象如果变为rejected状态,则reject的参数会被catch方法的回调函数接收到。
  • 任何一个await后面的Promise对象状态变为rejected,那么整个async函数都会中断执行。并且async函数返回的Promise对象状态为rejected
async function foo(){
  await Promise.reject('出错了');  // 相当于前面加了 return
  await Promise.resolve('abc');  // 不会执行
}

foo().then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)  // 出错了
})
  • 如果希望一个await异步操作失败不中断后续的执行,可以将await放在try catch中。这样无论await执行是否成功,都不会影响后续操作。
// 方法一
async function foo(){
  try{
    await Promise.reject('出错了');
  }catch(e){
    console.log(e)  // 出错了
  }
  return await Promise.resolve('abc');  // 会继续执行
}

// 方法二
async function foo(){
  await Promise.reject('出错了')
      .catch(e => console.log(e))  // 出错了
  return await Promise.resolve('abc');  // 会继续执行
}


foo().then(res => {
  console.log(res)  // 'abc' 
}).catch(err => {
  console.log(err)  
})

按顺序完成异步操作

  • 继发执行请求,当第一个await成功返回结果后,才会调用第二个,效率比较差。
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
  • 并发执行请求。虽然map方法的参数是async函数,但map是并发执行的,只有async函数内部是继发执行,而外部不受影响。
async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}