async / await

534 阅读4分钟

一、说说 async/await

从字面意思可以看出async用来声明一个函数是异步的,await用来等待一个异步方法执行完成。

1. async 起什么作用

和普通函数一样,async 函数直接调用就能执行,不像 Generator 需要使用 next。

async function fn() {
  return 'hello';
}
let p = fn();
console.log(p);  // Promise  { 'hello' }
p.then(() => console.log(2), () => console.log(3));  // 2

可以看出,async 函数返回的是一个 Promise 对象new Promise() 作用相同)。这是因为无论函数内返回的是直接量还是没有返回值,async 都会把它通过 Promise.resolve() 封装成 Promise 对象,因此返回的 Promise 实例当然也能 then 调用。

如果没有 await,那么 async函数调用后会立即执行,这不就和无等待的Promise一样了吗??

2. await 在等什么

虽然我们一般认为 await 是在等待 async 函数完成。但实际上,await 等的是一个返回值。提前说明一下,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西

  • 当 await 等到的是Promise对象时(async函数执行完后返回),await 会阻塞后面的代码,等着 Promise 对象 resolve,然后将 resolve 的值作为 await 表达式的运算结果。
  • 当 await 等到的不是Promise对象,那么 await 表达式的运算结果就是它等到的东西
function getSomething() {
  return 'hello';
}
async function getPromise() {
  return Promise.resolve('world');
}
async function test() {  
  let v1 = await getSomething();
  let v2 = await getPromise();  // 返回的是值,不是Promise对象
  console.log(v1, v2);
}
test();  
// hello world

所以,async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

3. async/await 帮我们干了啥

对于异步编程来说,async 会把返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,然后将 resolve 的结果返回出来

下面是常规的Promise异步写法,将异步操作包装一层避免Promise直接执行,然后用then指定回调函数。

function fn() {  // 包装一层,避免直接执行
  return new Promise((resolve) => {
    setTimeout(() => resolve('异步完成'), 1000);
  });
}
fn().then((data) => {
  console.log(data);
  console.log('回调执行完成');
});

将其改写成async,

function fn() {  // await 后面要跟的异步操作
  return new Promise((resolve) => {
    setTimeout(() => resolve('异步完成'), 1000);
  });
}

async function test() {
  let p = await fn();
  console.log(p);
  console.log('回调执行完成');
}
test();

异步操作不变,将返回的Promise对象用 await 等待。也就是说,async/await 重点是处理回调(then 的效果),将异步操作执行完的结果和回调函数串联。

3.1 处理 then 链

相比 Promise,async 函数将异步代码以同步的方式写出来,更为清晰。

function takeTime(n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 1000), n);
  });
}
function step1(n) {
  console.log(n);
  return takeTime(n);
}
function step2(n) {
  console.log(n);
  return takeTime(n);
}
function step3(n) {
  console.log(n);
  return takeTime(n);
}
async function test() {
  let t0 = 1000;
  let t1 = await step1(t0);
  let t2 = await step2(t1);
  let res = await step3(t2);
  console.log(res);
}
test();
// 立即输出1000,1s 后输出2000,2s 后输出 3000,3s 后输出4000

二、async 的错误处理机制

1. Promise 对象的状态变化

async 函数返回的 Promise 对象,必须等到内部所有 await 命令执行完,才会发生状态改变除非遇到 return 语句 或 抛出错误

2. await 后面 Promise.reject

任何一个 await 命令后面的Promise对象如果变成 reject 状态,那么整个 async 函数都会立刻中断执行,reject 的参数会被 catch 方法的回调函数接收

async function f() {
  await Promise.reject('出错了');  // 没有 return 也可以
  await Promise.resolve('hello');  // 不会执行
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错

有时候,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以用 try...catch

async function f() {
  try {
    await Promise.reject('出错了');  // 没有 return 也可以
  } catch (e) {
    console.log('Error');
  }
  return await Promise.resolve('hello');  // 不会执行
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error
// hello

3. 基础知识:async 对Generator 的改进

  • 直接调用函数即可,不需要 next。
  • 语义更清楚,
  • await 后面可以跟 Promise对象 和原始类型的值,yield 后面只能跟Promise对象。
  • async 函数返回值是Promise对象。

参考文章:

边城:理解 async/await