源码共读04:CO源码

103 阅读3分钟

tj大神写的co 仓库

整体架构源码分析 仓库

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。

这是源码共读的第4期,链接: juejin.cn/post/708498…

co源码之前,先看几段简单的代码

// 写一个简单的请求
function resuest(ms = 3000) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ name: 'sam' })
        }, ms);
    });
}
// 1. 获取generator的值
function* generatorFunc() {
    const res = yield resquest()
    console.log(res, 'generatorFunc-res');
}
generatorFunc(); // 不会自动执行

简单来说co,就是将generator自动执行,再返回一个promise。generator函数它不会自动执行,还要一步步调用next(),也就是调用一步走一步。

所有后来有了async、await函数。

async function asyncFunc() {
    const res = await resquest();
    console.log(res, 'asyncFunc-res await 函数 自动执行');
}
asyncFunc();

也就是说co需要做的事,是让generator像async、await函数一样自动执行。

模拟实现简版co(第一版)

根据generator的特性,其实很容易实现以下代码:

 // 获取generator的值
function* generatorFunc() {
    const res = yield request()
    console.log(res, 'generatorFunc-res')
}

function onSimple(gen) {
    gen = gen()
    console.log(gen, 'gen')
    
    const ret = gen.next()
    const promise = ret.value;
    promise.then(res => {
        gen.next();
    })
}

coSimple(generatorFunc);
// { name: 'sam' }"generatorFunc-res"

模拟实现简版co(第二版)

但是实际上,不会上面那么简单的。有可能是多个yield和传参数的情况。传参可以通过以下两行代码解决。

const args = Array.prototype.slice.call(arguments, 1);
gen = gen.apply(ctx, args)

两个yield,可以重新调用一下promise.then

 // 多个yeild,传参情况
function* generatorFunc(suffix = '') {
    const res = request()
    console.log(res + 'generatorFunc-res');
    
    const res2 = resquest()
    console.log(res2 + 'generatorFunc-res');
}

function coSimple(gen) {
    const ctx = this;
    const args = Array.prototype.slice.call(arguments, 1);
    gen.apply(ctx, args);
    console.log(gen, 'gen')
    
    const ret = gen.next();
    const promise = ret.value;
    promise.then(res => {
        const ret = gen.next(res)
        const promise = ret.value
        promise.then(res => {
            gen.next(res)
        })
    })
}

coSimple(generatorFunc, '------后缀');

模拟实现简版co(第三版)

问题是肯定不止两次,无限次的yield的呢,这时肯定要把重复的封装起来。而且返回的是promise,这就实现了如下版本的代码。

function* generator(suffix = '') {
    const res = yield request();
    console.log(res, 'generator-res' + suffix);
    const res1 = yield request();
    console.log(res1, 'generator-res2' + suffix);
    const res2 = yield request();
    console.log(res2, 'generator-res3' + suffix);
    const res3 = yield request();
    console.log(res3, 'generator-res4' + suffix);
}

function coSimple(gen) {
    const ctx = this;
    const args = Array.prototype.slice.call(arguments, 1);
    gen = gen.apply(ctx, args);
    console.log(gen, 'gen');

    return new Promise((resolve, reject) => {
        onFulfilled();
        
        function onFulfilled(res) {
            const ret = gen.next(res)
            next(ret);
        }
        
        function next(ret) {
            const promise = ret.value();
            promise && promise.then(onFulfilled);
        }
    })
}

coSimple(generatorFunc, '--------后缀');

第三版实现的简版co中,还没有考虑报错和一些参数合法的情况。

最终看下co源码

function co(gen) {
    const ctx = this;
    const args = slice.call(arguments, 1)
    
    return new Promise(function(resolve, reject) {
        if(typeof gen === 'function') gen.apply(ctx, args);
        if(!gen || typeof gen.next !== 'function') return resolve(gen);
        
        onFulfilled()
        
        function onFulfilled(res) {
            let ret
            try {
                ret = res.next()
            } catch(e) {
                return reject(res)
            }
            next(ret)
        }
        function onRejected(err) {
            let ret
            try {
                ret = gen.throw(err)
            } catch(e) {
                return reject(e)
            }
            next(ret)
        }
        // 反复执行自己
        function next(ret) {
            // 检查当前是否为Generator函数的最后一步,如果是就返回
            if(ret.done) return resolve(ret.value)
            // 确保返回值是promise对象
            let value = toPromise.call(ctx, ret.value)
            // 使用then方法,为返回值加上回到函数,然后通过onFulfilled函数再次调用next函数
            if (value && toPromise(value)) return value.then(onFulfilled, onRejected);
            // 在参数不符合要求的情况下(参数非Thunk函数和Promise对象),将Promise对象的状态改为rejected,从而终止执行。
            return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
            + 'but the following object was passed: "' + String(ret.value) + '"'))
        }
    })
    
}

此文章为12月Day2源码共读,以梦为马,抓住2023年的尾巴💪💪