babel是如何实现async/await的

吐槽君 分类:javascript

async跟await是javascript中常用的关键字,用于使用同步代码实现异步功能,但由于async/await是ES7标准中定义的,虽然很多浏览器早先实现了async/await,但很多时候还是需要考虑兼容性问题,它的兼容性如下:

image.png

一般来说,使用babel等对代码进行编译就可以避免兼容性问题,那它是怎么转换的呢,答案是使用生成器函数:

假设我们有下面的代码:

async function a(name) {
    let ret = await b(name);
    console.log(ret);
    return ret + ' and Mark';
}

function b(name) {
    return Promise.resolve('name: ' + name);
}

a('Bill').then(value => console.log(value));
 

来看看转换的结果:

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 a() {
  return _a.apply(this, arguments);
}

function _a() {
  _a = _asyncToGenerator(function* (name) {
    var ret = yield b(name);
    console.log(ret);
    return ret + ' and Mark';
  });
  return _a.apply(this, arguments);
}

function b(name) {
  return Promise.resolve('name: ' + name);
}

a('Bill').then(value => console.log(value));
 

由于babel转换的代码包含了很多边界情况,所以显得逻辑复杂,我们将它简化就可以明白它的主要逻辑了:

  1. 将async函数转换为生成器函数(GeneratorFunction)

还是假设我们有a和b函数,转换之后的样子如下

function* _a(name) {
  let ret = yield b(name);
  console.log(ret);
  return a + ' and Mark';
}

function b(name) {
  return Promise.resolve('name: ' + name);
}
 

为了不影响原始代码,我们将_a函数包裹在a函数内部:

function a(name) {
    var gen = _a(name);
    var ret = gen.next();
    // ret.value 是b()的返回值
    return ret.value.then(v => {
        var ret = gen.next(v);
        // 此时的ret.value是_a()函数的返回值
        return ret.value;
    });
}
 

好了,像上面这样确实已经可以实现主要的功能了,但是存在一个问题,就是每出现一个yield就要嵌套一层then,这个过程如果能够自动化那么代码会简洁不少,于是我们就有了runGen和step函数:

  1. 实现step函数
function step(gen, value) {
  const ret = gen.next(value);
  if (ret.done) {
    return ret.value;
  } else {
    return ret.value.then((v) => {
      return step(gen, v);
    });
  }
}

function runGen(genFn, ...args) {
  let gen = genFn(...args);
  return step(gen);
}

function a(name) {
  return runGen(_a, name);
}
 
  1. 运行结果
a('Bill').then(v => {
  console.log(v);
});
 

好了,到这里就差不多实现了一个类似async函数的生成器函数了。

仔细看看会发现,babel实现的_asyncToGenerator这个函数(相当于上文的runGen)返回的是一个new Promise(),并且分别实现了_next和_throw函数,这可以更好地捕获处理生成器中的错误。

回复

我来回复
  • 暂无回复内容