【手写 Promise 源码】第十七篇 - async/await 简介

579 阅读2分钟

一,前言

上一篇,主要介绍了 co 库的使用和实现原理,主要涉及以下几个点:

  • co 库的简介:特性、用法、功能分析;
  • co 库的实现和原理分析;

本篇,继续介绍 async/await:

  • async/await 是基于 generator 的语法糖
  • async/await 是 generator + co 的组合;
  • async/await 是“回调地狱”问题的终极解决方案;

二,async/await 的使用

  • 测试场景:读取 a.txt得到结果 b.txt;读取 b.txt得到结果 c;
const util = require('util');
const fs = require('fs');
let readFile = util.promisify(fs.readFile)

// generator 生成器函数
function * read() {
  let data = yield readFile('./a.txt','utf8');
  data = yield readFile(data,'utf8');
  return data;
}

// 方法一:Generator 生成器
let it = read(); // 生成器函数返回一个 Iterator 迭代器对象
let {value:v1, done:d1} = it.next(); // 注意:第一个next传参是无效的
v1.then(data=>{
  console.log(data) // b.txt
  let {value:v2, done:d2} = it.next(data); // 将第一次的结果作为第二次的入参
  return v2
}).then(data=>{
  console.log(data) // 执行结果:c
})

// 方法二:使用 co 库
const co = require('co')
// co 方法:包装 generator 生成器函数,内部自动执行完成,返回一个 promise
co(read()).then(data=>{
  console.log(data);  // 执行结果:c
})

// 方法三:使用 async/await
async function read() {
  let data = await readFile('./a.txt','utf8');
  data = await readFile(data,'utf8');
  return data;
}

// async 函数执行完成后,返回一个 promise
read().then(data=>{
  console.log(data)
})

之前,使用 generator + co 组合,已经让代码看上去很像是同步了,但仍需要 co 库的支持;

使用 async/await:在外层 function 前添加 async;await 右侧的方法返回 promise;无需 co 库支持即可实现;

这样,代码看上去与“同步编码”无任何明显差异,有效避免了嵌套回调问题;


三,async/await 的实现

还是将 async/await 代码放入 babel.io,查看编译后的代码:

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function () {
    var self = this, args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      } function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      } _next(undefined);
    });
  };
}

function read() {
  return _read.apply(this, arguments);
}

function _read() {
  _read = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    var data;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return readFile('./a.txt', 'utf8');

          case 2:
            data = _context.sent;
            _context.next = 5;
            return readFile(data, 'utf8');

          case 5:
            data = _context.sent;
            return _context.abrupt("return", data);

          case 7:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _read.apply(this, arguments);
}

以上代码可以看出,_read函数就是之前实现的 generator 函数,这就说明 async 最终会被转换成为 generator 生成器;

执行过程分析:

  • 第一次调用异步迭代方法,传入 undefined:_next(undefined);,进入asyncGeneratorStep方法:gen 是 it 迭代器,key 是 next 方法;
  • 如果没完成,继续调用_next(递归),继续取出下一个执行异步迭代;

所以,async/await 其实也就是 generator + co 的语法糖;

编码中,所有异步回调代码可以全部采用 async/await 写法替换,可以有效解决回调嵌套问题;


四,结尾

本篇,主要介绍了 async/await 的使用和实现原理,主要涉及以下几个点:

  • async/await 的使用和功能分析;
  • async/await 的实现和原理分析;

下一篇,继续介绍浏览器的事件环 EventLoop;