用generator实现async+await

2,589 阅读2分钟

近期在看redux-saga源码,了解take,put,fork,takeEvery,all等的实现,里面全是generator(saga里面不可以用async+await)...

事实上除了在saga里面要写大量generator之外,我们平时在业务代码中更多会用async + await来处理异步逻辑。不过,了解generator是实现async+await基础。

简言之,async + await = co + generator + 返回promise + try catch处理错误捕获。

需要实现效果,应该和以下async+await的结果一致:

async function myAsync() {
    let a = await Promise.resolve(1);
    let b = await Promise.resolve(2);
    let c = await Promise.resolve(3);
    return a + b + c;
}
myAsync().then(res => {
    console.log('async的结果:', res); // async的结果: 6
})

第一版: co + generator + 返回promise:

function* myGenerator() {
    let a = yield Promise.resolve(1);
    let b = yield Promise.resolve(2);
    let c = yield Promise.resolve(3);
    return a + b + c;
}

//期望:
run(myGenerator).then(res => {
    console.log('mygenerator函数的结果,', res); // mygenerator函数的结果, 6
})

//实现run方法
function run(generator) {
    return new Promise((resolve, reject) => {
        let it = generator();
        const next = (newValue) => {
            let {value, done} = it.next(newValue);
            if (!done) {
                Promise.resolve(value).then(next); 
                // 用Promise.resolve包裹value值是预防yield后面为primitive值的情况
            } else {
                resolve(value)
            }
        }
        next();
    })
}
// 以上实现基本功能,下面看看如何进行错误捕获呢?

第二版: 加上错误捕获的处理:

首先需要了解一个知识点:在生成器generator执行后返回的迭代器iterator上有一个throw方法,可以接收一个错误信息,并且把它抛出取,如果generator函数里有try catch,则iterator抛出来的错误,可以被generator函数中的catch(e)给捕获到。

function* myGenerator2() {
    try {
        yield 1;
        yield Promise.reject('我错啦😂!'); 
        // 猜猜这里的错误,哪里会捕获到?是下面的catch?还是最后promise的catch? 
    } catch (error) {
      console.log('generator函数里面catch到的错误', error)
    }
}

function run(generator) {
    return new Promise((resolve, reject) => {
        let it = generator();
        const next = (newValue) => {
            let res;
            try {
                res = it.next(newValue);
            } catch(err) {
                return reject(err)
            }
            
            console.log('res', res); // 介里会是什么呢?
            // res { value: Promise { <rejected> '我错啦😂!' }, done: false }
            
            if (!res.done) {
                Promise.resolve(res.value).then(next, err => it.throw(err)); // Here 看过来!
            } else {
                resolve(res.value);
            }
        }
        next();
    })
}

run(myGenerator2).then(res => {
    console.log('mygenerator函数的结果,', res);
}).catch(e => {
    console.log('放心,这里catch到啦!', e); // 是我这里捕获到了么?
})

// 答案来喽,不想看请蒙住:

// generator函数里面catch到的错误 我错啦😂!

可以发现,yield 后面函数处理发生错误,但是只要内部用it.throw()把接收到的错误扔出去,那么在外层generator函数里,只要写了try catch就可以被catch捕获到。出错后,迭代器便不往后继续迭代了。

MDN的解释:使用 throw方法向该生成器抛出一个异常,该异常通常可以通过 try...catch 块进行捕获。

那如果内部不使用throw,而是用reject去处理错误呢?即:

if (!res.done) {
    Promise.resolve(res.value).then(next,reject); // 这里变成reject
} else {
    resolve(res.value);
}
// 那么就是最后的catch方法接到了错误:“放心,这里catch到啦! 👀”

情况大致就是这样啦,下面9k字常文总结得更全面~ /:moon

参考文献:

异步编程二三事 | Promise/async/Generator实现原理解析 | 9k字

MDN: Generator.prototype.throw()