Skip to content

Instantly share code, notes, and snippets.

@zxhfighter
Created February 28, 2018 13:34
Show Gist options
  • Save zxhfighter/969624413b8da519f0217b1e46a350bc to your computer and use it in GitHub Desktop.
Save zxhfighter/969624413b8da519f0217b1e46a350bc to your computer and use it in GitHub Desktop.
the async/await manual

async/await 使用指南

[TOC]

Callback Hell

在新的异步编程规范出来之前,很容易写出多层嵌套的代码。

const main = (paramA, paramB, paramC, done) => {
  funcA(paramA, (err, resA) => {
    if (err) {
      return done(err)
    }

    return funcB(paramB, (err, resB) => {
      if (err) {
        return done(err)
      }

      funcC(paramC, (err, resC) => {
        if (err) {
          return done(err)
        }

        return done(null, { resA, resB, resC })
      })
    })
  })
}

Generator 函数

Generator 函数是 ES6 提出的一种异步编程解决方案,可以将 Generator 函数理解为一个状态机,封装了多个内部状态。

将上面的代码改写成 Generator 的方式如下:

function funcA(param) {
  return param + 1;
}

function funcB(param) {
  return param + 2;
}

function funcC(param) {
  return param + 3;
}

function* main(paramA, paramB, paramC) {
  const resA = yield funcA(paramA);
  const resB = yield funcB(paramB);
  const resC = yield funcC(paramC);
}

const gen = main('1', '2', 3);
const iter = gen.next();
while (!iter.done) {
  console.log(iter.value);
  iter = gen.next();
}

可以看到,使用 Generator 生成器函数需要手动去调用 next() 方法,去执行相关的异步代码,还是不太方便。

async/await

ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数可以看做 Generator 函数的语法糖。

上面的代码可以用 async/await 改写成:

function funcA(param) {
  return Promise.resolve(param + 1);
}

function funcB(param) {
  return Promise.resolve(param + 2);
}

function funcC(param) {
  return Promise.resolve(param + 3);
}

const main = async (paramA, paramB, paramC) => {
  const resA = await funcA(paramA);
  const resB = await funcB(paramB);
  const resC = await funcC(paramC);

  return { resA, resB, resC };
}

main('1', '2', 3).then(console.log);

async/await 和 Generator 的区别

  • 内置执行器,Generator 函数的执行必须靠执行器(例如 next() 方法,或者第三方 co 模块)
  • 更好的语义
  • 更广的适用性,yield 命令后边只能是 Thunk 函数或 Promise 对象,而 async 函数中 await 命令后边可以是 Promise 对象和原始类型的值
  • 返回值是 Promise,而 Generator 函数执行后返回的是 Iterator 对象

异常处理

你总是将生活想象的太美好,如果上边的函数出错了呢?谁来承担处理错误?

你可能会这么做,针对每一个可能的错误,使用 try/catch 来捕获。

const main = async (paramA, paramB, paramC) => {
  let resA, resB, resC;

  try {
    resA = await funcA(paramA);
  } catch (error) {
    throw error;
  }

  try {
    resB = await funcB(paramB);
  } catch (error) {
    throw error;
  }

  try {
    resC = await funcC(paramC);
  } catch (error) {
    throw error;
  }

  return { resA, resB, resC };
}

但是,实际的情况是,有时候并不需要针对每一个错误单独处理,因此可以将可能发生错误的代码一网打尽,可以根据错误对象来处理。

const main = async (paramA, paramB, paramC) => {
  try {
    const resA = await funcA(paramA);
    const resB = await funcB(paramB);
    const resC = await funcC(paramC);

    return { resA, resB, resC };
  } catch (error) {
    // you can analyze the structure of the error object
    throw error;
  }
}

最后,在 async 函数中抛出的异常,需要由调用 async 函数的宿主函数来负责捕获。

const main = async (paramA, paramB, paramC) => {
  try {
    const resA = await funcA(paramA);
    const resB = await funcB(paramB);
    const resC = await funcC(paramC);

    return { resA, resB, resC };
  } catch (error) {
    // you can analyze the structure of the error object
    throw error;
  }
}

main('1', '2', 3)
  .then(d => console.log('do something awesome with the result'))
  .catch(e => console.log('handle that error!'));

不过,之前提到 async 函数返回的都是 Promise 对象,因此我们甚至都不需要在 async 中使用多此一举的 try/catch

// Since each of the await calls will trigger the `.catch` if they fail...
const main = async (paramsA, paramsB, paramsC) => {
  const resA = await funcA(paramsA);
  const resB = await funcB(paramsB);
  const resC = await funcC(paramsC);

  return { resA, resB, resC };
}

// ... all we need is this `.catch` to handle all of them.
main('1', '2', 3)
  .then(d => console.log('do something awesome with the result'))
  .catch(e => console.log('handle that error!'));

另外,可以将错误的单独处理和通用处理结合在一起,如下:

const main = async (paramsA, paramsB, paramsC) => {
  const resA = await funcA(paramsA);
  const resB = await funcB(paramsB).catch(e => console.log('things unique to this error'));
  const resC = await funcC(paramsC);

  return { resA, resB, resC };
}

// ... all we need is this `.catch` to handle all of them.
main('1', '2', 3)
  .then(d => console.log('do something awesome with the result'))
  .catch(e => console.log('handle that error!'));

使用 Promise.all 来实现并发

如果一些异步函数之间没有依赖关系,可以使用 Promise.all() 来改善请求时间。

const main = async (paramsA, paramsB, paramsC) => {
  const resA = await funcA(paramsA);

  // if resB and recC have no dependency on each other
  // they can be requested parallel
  const [resB, resC] = await Promise.all([funcB(paramsB), funcC(paramsC)]);

  return { resA, resB, resC };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment