理解 async 和 await

200 阅读5分钟

async、await是js解决异步编程的一种方案

async function用来声明一个异步函数,这个异步函数返回一个Promise
await后面接一个会返回Promise的函数并执行这个函数,await会暂停async代码的执行,直到Promise执行完
使用asyncawait会让异步函数的语法和结构更像同步函数

async语法

async function f() {
    return 1;
}
等价于
function foo() {
   return Promise.resolve(1)
}

async会返回一个promise

返回的Promise对象会运行执行(resolve)异步函数的返回结果,或者运行拒绝(reject)
如果function中返回的是一个值,async直接会用Promise.resolve()包裹一下返回
async function f() {
    return 1;
}
f().then((res) => {
    console.log(res)     // 1
}) 

async函数内部return语句返回的值,会成为then方法回调函数的参数

async function f() {
  return 'hello world';
}
f().then(v => console.log(v))     // "hello world"

await操作符

用于等待一个Promise对象,它只能在异步函数async中使用,await会暂停async的执行,等待Promise处理完成
await的返回值返回Promise对象的处理结果,如果等待的不是Promise对象,则返回该值本身
若Promise异步操作成功,其回调的resolve函数参数作为await表达式的值,继续执行asyncPromise异步操作失败,await会把Promise的异常原因抛出
async在没有遇到await之前async函数是同步运行,遇见await会异步执行
在await表达式之后的代码可以被认为是存在在链式调用的then回调方法中

async function foo(){
  await 1
}
等价于
function foo() {
   return Promise.resolve(1).then(() => undefined)
}

错误处理(await只等待一个结果,异步发生错误时,如何捕获错误)

使用用try-catch来做错误捕捉
async function myFunction() {
  try {
    await Promise.reject('1');
  } catch (err) {
    console.log(err);
  }
}
myFunction(); // 1

用promise的catch来做错误捕捉
async function myFunction() {
    await Promise.reject('1').catch((err) => {
        console.log(err);
    });
}
myFunction(); // 1

async函数内部抛出错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数接收到

async function f() {
  throw new Error('出错了');
}
f().then(
  v => console.log(v),
  e => console.log(e)
)

async 返回的Promise对象的状态变化

必须等内部所有await命令后面的Promise对象执行完,才会发生状态改变,或者遇到return语句或者抛出错误
只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
只有函数getTitle内部的三个操作全部完成,才会执行then方法里面的console.log

await后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到

async function f() {
  await Promise.reject('出错了');
}
f().then(v => console.log(v)).catch(e => console.log(e))

任何一个await后面的Promise对象变为reject状态,那么整个async函数都会中断执行

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world');  // 不会执行
}

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f().then(v => console.log(v)).catch(e => console.log(e))
f执行后,await后面的Promise会抛出一个错误对象,导致catch的回调函数被调用,它的参数就是抛出的错误对象

防止出错,使用try catch

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

如果有多个await命令,可以统一放在try catch中

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);
    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

使用 async和await 的注意事项

1.await命令后面的Promise对象,运行结果可能是rejected,最好把await命令放在try catch2.多个await命令后面的异步操作,如果不存在继发关系(互相不依赖),最好让它们同时触发
let foo = await getFoo();
let bar = await getBar();
等价于
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

3.await命令只能用在async函数之中,如果用在普通函数,就会报错

async 和 Promise 的区别

通过示例:在DOM元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个
如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值

使用 Promise
function chainAnimationsPromise(elem, animations) {
  let ret = null;                     // 变量ret用来保存上一个动画的返回值
  let p = Promise.resolve();          // 新建一个空的Promise
  for(let anim of animations) {       // 使用then方法,添加所有动画
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }
  return p.catch(function(e) {         // 返回一个部署了错误捕捉机制的Promise
  }).then(function() {                 // 忽略错误,继续执行
    return ret;
  });
}
Promise 的写法全都是PromiseAPI(then、catch等),操作本身的语义反而不容易看出来

使用 async
async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {                          // 忽略错误,继续执行
  }a
  return ret;
}
async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码

实例:按顺序完成异步操作(依次远程读取一组 URL,然后按照读取的顺序输出结果)

Promise 的写法如下
function logInOrder(urls) {
  const textPromises = urls.map(url => {           // 远程读取所有URL
    return fetch(url).then(response => response.text());
  });
  textPromises.reduce((chain, textPromise) => {    // 按次序输出
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}
使用fetch方法,同时远程读取一组URL,每个fetch操作都返回一个Promise对象,放入textPromises数组
reduce方法依次处理每个Promise对象,然后使用then,将所有Promise对象连起来,因此就可以依次输出结果

async 函数实现
async function logInOrder(urls) {
  const textPromises = urls.map(async url => {     // 并发读取远程URL
    const response = await fetch(url);
    return response.text();
  });
  for (const textPromise of textPromises) {        // 按次序输出
    console.log(await textPromise);
  }
}
map方法的参数是async函数,它是并发执行的,因为只有async函数内部是继发执行,外部不受影响
后面的for of循环内部使用了await,因此实现了按顺序输出

总结

async/await 是基于Promise概念的解决异步操作方法,从语义上讲可读性更强,更加容易使用