ES7加入的async函数,终于让 JavaScript 对于异步操作有了终极解决方案。No more callback hell。

async 函数是Generator函数的语法糖。使用关键字async来表示,在函数内部使用await来表示异步。

相较于GeneratorAsync函数的改进在于下面四点:

  • 内置执行器。Generator函数的执行必须依靠执行器,而Aysnc函数自带执行器,调用方式跟普通函数的调用一样

  • 更好的语义。asyncawait相较于*yield更加语义化

  • 更广的适用性。co模块约定,yield命令后面只能是Thunk函数或Promise对象。而async函数的await命令后面则可以是Promise或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)

  • 返回值是Promiseasync函数返回值是Promise对象,比Generator函数返回的Iterator对象方便,可以直接使用then()方法进行调用

语法

async function getUserByAsync(){
  let user = await fetchUser();

  return user;
}

getUserByAsync()
  .then(v => console.log(v));

凡是在function前面添加了async的函数在执行后都会自动返回一个Promise对象

await必须在async函数里使用,不能单独使用

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

async function fn() {
  const promise1Value = await promise1();

  return promise1Value;
}

fn()
  .then(res => {
    console.log(res); // promise1Value
  })

async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
  await delay(1000);
  await delay(2000);
  await delay(3000);
  return 'done';
}

f().then(v => console.log(v)); // 等待6s后才输出 'done'

await后面需要跟Promise对象,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。

正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise

async function  f() {
  return await 1
};

f().then( (v) => console.log(v)) // 1

错误处理

async函数抛出的错误会被catch捕获

async function fn() {
  throw new Error('this is an error');
}

fn()
  .catch(err => {
    console.log(err); // this is an error
  })

async函数中只要一个await出现reject状态,则后面的await都不会被执行

let a;
async function f() {
    await Promise.reject('error');
    a = await 1; // 这段 await 并没有执行
}
f().then(v => console.log(a));

这样第一个只要reject, 那么这个函数基本算没用了,所以我们暂时可以使用try/catch来解决。

// 正确的写法
let a;
async function correct() {
    try {
        await Promise.reject('error')
    } catch (error) {
        console.log(error);
    }
    a = await 1;
    return a;
}

correct().then(v => console.log(a)); // 1

这样一个await写一个try/catch非常麻烦,可以封装成一个函数

async function errorCaptured(asyncFunc) {
  try {
    let res = await asyncFunc;
    return [null, res];
  } catch(err) {
    return [err, null];
  }
}

ts的写法

function awaitErrorCaptured<T, U = any>(promise: Promise<T>): Promise<[U | null, T | null]> {
 return promise
  .then<[null, T]>((data: T) => [null, data])
  .catch<[U, null]>(err => [err, null])
}

使用

async function func() {
  let [err, res] = await errorCaptured(asyncFunc)
  if (err) {
      //... 错误捕获
  }
  //...
}

async/await hell

await firstFn(); // show off await magic!!
await secondFn(); // why am i waiting for firstFn?
await thirdFn(); // I am waiting for firstFn & secondFn
await fourthFn() // hold on, why are we waiting so much
await fifthFn() // ‘coz someone doesn't konw await correctly’
await sixthFn() // this is what hell look like ...

在实际使用中,避免这种地狱写法,可以使用下面的写法。

Promise.all([firstFn, secondFn, ...])
  .then()

当如果真正我们需要上一个promise的值的话,再用await

async function fn() {
  const firstFnResult = await firstFn();

  secondFn(firstFnResult);
}