Skip to content

async / await 底层的 generator 实现 #183

Open
@TieMuZhen

Description

@TieMuZhen

我们要如何实现一个async/await呢,首先我们要知道,async/await实际上是对Generator(生成器)的封装,是一个语法糖。

因此我们先来看看Generator的用法:

ES6 新引入了Generator函数,可以通过yield关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。

function* myGenerator() {
  yield '1'
  yield '2'
  return '3'
}

const gen = myGenerator();  // 获取迭代器
gen.next()  //{value: "1", done: false}
gen.next()  //{value: "2", done: false}
gen.next()  //{value: "3", done: true}   如果上面不是return '3',而是yield '3'则返回{value: undefined, done: true}

也可以通过给next()传参, 让yield具有返回值

function* myGenerator() {
  console.log(yield '1')  //test1
  console.log(yield '2')  //test2
  console.log(yield '3')  //test3
}

// 获取迭代器
const gen = myGenerator();

gen.next()
gen.next('test1')
gen.next('test2')
gen.next('test3')

我们看到Generator的用法,应该️会感到很熟悉,*/yieldasync/await看起来其实已经很相似了,它们都提供了暂停执行的功能,但二者又有三点不同:

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await能够返回Promiseresolve/reject的值

我们对async/await的实现,其实也就是对应以上三点封装Generator。

function run(gen) {
  var g = gen()                     //由于每次gen()获取到的都是最新的迭代器,因此获取迭代器操作要放在_next()之前,否则会进入死循环

  function _next(val) {             //封装一个方法, 递归执行g.next()
    let {value, done} = g.next(val)           //获取迭代器对象,并返回resolve的值
    if(done) return value   //递归终止条件
    value.then(val => {         //Promise的then方法是实现自动迭代的前提
      _next(val)                    //等待Promise完成就自动执行下一个next,并传入resolve的值
    })
  }
  _next()  //第一次执行
}

对于我们之前的例子,我们就能这样执行:

function* myGenerator() {
  console.log(yield Promise.resolve(1))   //1
  console.log(yield Promise.resolve(2))   //2
  console.log(yield Promise.resolve(3))   //3
}

run(myGenerator)

返回Promise & 异常处理

虽然我们实现了Generator的自动执行以及让yield返回resolve的值,但上边的代码还存在着几点问题:

  • 返回值是Promiseasync/await的返回值是一个Promise,我们这里也需要保持一致,给返回值包一个Promise
  • 需要兼容基本类型:这段代码能自动执行的前提是yield后面跟Promise,为了兼容后面跟着基本类型值的情况,我们需要把yield跟的内容都用Promise.resolve()转化一遍
  • 缺少错误处理:上边代码里的Promise如果执行失败,就会导致后续执行直接中断,我们需要通过调用Generator.prototype.throw(),把错误抛出来,才能被外层的try-catch捕获到

我们改造一下run方法:

function run(genetator){
    return new Promise((resolve, reject) => {
        let interator = genetator();
        function _next(val){
            try {
                var {value, done} = interator.next(val)
            } catch (error) {
                return reject(error);
            }
            if(done){
                return resolve("{ value: undefined, done: true }"); 
            }
            Promise.resolve(value).then((v) => {
                _next(v);
            }, (error) => {
                interator.throw(error);
            })
        }
        _next();
    })
}

然后我们可以测试一下:

function* myGenerator() {
    let res1 = yield 1;
    console.log(res1)   //1
    let res2 = yield 2;
    console.log(res2)   //2
    let res3 = yield Promise.resolve(3);
    console.log(res3)   //3
}

let res = run(myGenerator)
res.then((d) => {
    console.log(d); // { value: undefined, done: true }
})

参考文献

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions