004 CO 函数库

7/21/2021 Javascript

# 什么是 co 函数库

co 函数库 (opens new window)是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。

比如,有一个 Generator 函数,用于依次发送两个请求

function* foo() {
    let res = yield fetch("https://jsonplaceholder.typicode.com/todos/1");
    console.log("res1", res);
    let res2 = yield fetch("https://jsonplaceholder.typicode.com/todos/2");
    console.log("res2", res2);
}
1
2
3
4
5
6

CO 函数就可以让开发者不用自己编写执行器

var co = require('co');
co(foo);
1
2

只要传入 Generator 函数就会自动执行,CO 函数会返回一个 promise 对象,因此可以用 then 方法添加回调函数。

co(foo).then(function (){
  console.log('Generator 函数执行完成');
})
1
2
3

# 原理

自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

  1. 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权
  2. Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权

使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象

# 基于 Promise 对象的自动执行

沿用上面的例子,我们来手动执行这个 Generator 函数

function* foo() {
    let res = yield fetch("https://jsonplaceholder.typicode.com/todos/1").then(res => res.json());
    console.log("res1", res);
    let res2 = yield fetch("https://jsonplaceholder.typicode.com/todos/2").then(res => res.json());
    console.log("res2", res2);
}
1
2
3
4
5
6

手动执行上面的 Generator 函数

const gen = foo()
gen.next().value.then(res => {
  gen.next(res).value.then(res => {
    gen.next(res)
  })
})
1
2
3
4
5
6

自动执行代码

function run(gen) {
    const g = gen()
    function next(data) {
        const res = g.next(data)
        if (res.done) {
            return
        }
        res.value.then(data => {
            next(data)
        })
    }
    next()
}
run(foo); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

co 就是上面那个自动执行器的扩展 源码 (opens new window)

function co(gen) {
    var ctx = this;

    return new Promise(function (resolve, reject) {
        if (typeof gen === 'function') gen = gen.call(ctx);
        if (!gen || typeof gen.next !== 'function') return resolve(gen);

        onFulfilled();
// co 将 Generator 函数的内部指针对象的 next 方法,包装成 onFulefilled 函数。这主要是为了能够捕捉抛出的错误
        function onFulfilled(res) {
            var ret;
            try {
                ret = gen.next(res);
            } catch (e) {
                return reject(e);
            }
            next(ret);
        }

        function next(ret) {
            if (ret.done) return resolve(ret.value);
            var value = toPromise.call(ctx, ret.value);
            if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
            return onRejected(new TypeError(
                'You may only yield a function, promise, generator, array, or object, ' +
                'but the following object was passed: "' + String(ret.value) + '"'));
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

最后,就是关键的 next 函数,它会反复调用自身,next 函数的内部代码,一共只有四行命令

  1. 检查当前是否为 Generator 函数的最后一步,如果是就返回
  2. 确保每一步的返回值,是 Promise 对象
  3. 使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数
  4. 参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行

# 并发

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步,这时,要把并发的操作都放在数组或对象里面

// 数组的写法
co(function* () {
    var res = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
    console.log(res);
}).catch(onerror);

// 对象的写法
co(function* () {
    var res = yield {
        1: Promise.resolve(1),
        2: Promise.resolve(2),
    };
    console.log(res);
}).catch(onerror);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

参考

阮一峰 (opens new window)

Last Updated: 8/4/2021, 6:25:04 PM