Javascript异步编程(四: co 函数库)

前言

co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行,让你告别手动写执行器的痛苦。

https://github.com/tj/co

继续用上一章的thunk 函数,用于依次读取两个文件。
新建1.txt, 2.txt。
分别写入数据:测试数据1。测试数据2

const thunkify = require('thunkify');
const fs = require('fs');
const readFile = thunkify(fs.readFile);

const gen = function* () {
    const basic = yield readFile('1.txt','utf8');
    console.log(basic.toString()); 
    const package = yield readFile('2.txt','utf8');
    console.log(package.toString());
}

我们将Generator 函数的执行器改成co函数库。
并且,co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。

const co = require('co');
co(gen).then(function (){
    console.log('Generator 函数执行完成');
})

// 运行结果为
测试数据1
测试数据2
Generator 函数执行完成

上面代码的then方法,代表 Generator 函数执行结束,就会输出一行提示。

co 函数库的原理

为什么 co 可以自动执行 Generator 函数?

前面文章说过,Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

两种方法可以做到这一点。

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

那么co函数库做了什么事呢?

co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。

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

上一章已经介绍了基于 Thunk 函数的自动执行器。下面来看,基于 Promise 对象的自动执行器。这是理解 co 函数库必须的。

基于 Promise 对象的自动执行

首先,把 fs 模块的 readFile 方法包装成一个 Promise 对象。

const fs = require('fs');

const readFile = (fileName) =>{
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName,(err,data)=>{
            if(err) reject(err);
            resolve(data);
        })
    })
}

const gen = function* () {
    const basic = yield readFile('1.txt','utf8');
    console.log(basic.toString());
    const package = yield readFile('2.txt','utf8');
    console.log(package.toString());
}

然后,手动执行上面的 Generator 函数。

const g = gen();
g.next().value.then((data) => {
    g.next(data).value.then((data) => {
        g.next(data);
    });
});

利用递归实现一个自动执行器

const run = gen => {
    const g = gen();

    function next (data) {
        const result = g.next(data);
        if(result.done) return result.value;

        result.value.then(data => {
            next(data);
        })
    }
    next();
};

run(gen);

上面代码,只要 Generator 函数还没执行到最后一步,next 函数就递归调用自身,以此实现自动执行。

解析co 函数库的源码

co 就是上面那个自动执行器的扩展,它的源码只有几十行,非常简单。

https://github.com/tj/co/blob/master/index.js

首先,co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  return new Promise(function(resolve, reject) {

  });
}

在返回的 Promise 对象里面,co 先检查参数 gen 是否为 Generator 函数。

如果是,就执行该函数,得到一个内部指针对象;如果不是就返回,并将 Promise 对象的状态改为 resolved 。

return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') 
        gen = gen.apply(ctx, args);

    if (!gen || typeof gen.next !== 'function') 
        return resolve(gen);
});

接着,co 将 Generator 函数的内部指针对象的 next 方法,包装成 onFulefilled 函数。这主要是为了能够捕捉抛出的错误。

return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') 
        gen = gen.apply(ctx, args);

    if (!gen || typeof gen.next !== 'function') 
        return resolve(gen);

        onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }
});

最后,就是关键的 next 函数,它会递归调用自身。

    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) + '"'));
    }

上面代码中,next 函数的内部代码,一共只有四行命令。

第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。

第二行,确保每一步的返回值,是 Promise 对象。

第三行,使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。

第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。

并发的异步操作

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。

这时,要把并发的操作都放在数组或对象里面。

const thunkify = require('thunkify');
const fs = require('fs');
const readFile = thunkify(fs.readFile);
const co = require('co');

// 数组的写法
co(function* () {
    const res = yield [
        readFile('1.txt','utf8'),
        Promise.resolve(1),
        readFile('2.txt','utf8'),
        Promise.resolve(2),
    ];
    console.log(res); // => [ '测试数据1', 1, '测试数据2', 2 ]
}).catch(onerror);

// 对象的写法
co(function* () {
    const res = yield {
        1: Promise.resolve(1),
        'readFile1': readFile('1.txt','utf8'),
        4: Promise.resolve(1),
        'readFile2': readFile('2.txt','utf8'),
    };
    console.log(res);  
    // => { '1': 1, '4': 1, readFile1: '测试数据1', readFile2: '测试数据2' }
}).catch(onerror);

function onerror(err) {
    console.error(err.stack);
}

上面代码,因为是并行, 在对象内,4 操作不需要等待readFile1 完成再执行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值