Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

co源码分析 #4

Open
brunoyang opened this issue Sep 27, 2015 · 2 comments
Open

co源码分析 #4

brunoyang opened this issue Sep 27, 2015 · 2 comments

Comments

@brunoyang
Copy link
Owner

brunoyang commented Sep 27, 2015

仓库:github.com/tj/co

co是一款将generatorpromise结合起来的流程控制库。

本文根据co@4.6.0版本所撰写。

什么是generator

使用方法

1.将promise作为返回值

'use strict';
const fs = require('fs');
const co =require('co');

function read(filename) {
  return new Promise(function(resolve, reject) {
    fs.readFile(filename, 'utf8', function(err, res) {
      if (err) {
        return reject(err);
      }
      return resolve(res);
    });
  });
}

co(function *() {
  return yield read('./a.js');
}).then(function(res){
  console.log(res);
});

2.将数组作为返回值

'use strict';
const fs = require('fs');
const co =require('co');

function read(filename) {
  return new Promise(function(resolve, reject) {
    fs.readFile(filename, 'utf8', function(err, res) {
      if (err) {
        return reject(err);
      }
      return resolve(res);
    });
  });
}

co(function *() {
  const resA = read('./a.js');
  const resB = read('./b.js');
  const resC = read('./c.js');

  return yield [resA, resB, resC];
}).then(function(res){
  console.log(res);
  // [a file content, b file content, c file content]
});

3.将对象作为返回值

'use strict';
const fs = require('fs');
const co =require('co');

function read(filename) {
  return new Promise(function(resolve, reject) {
    fs.readFile(filename, 'utf8', function(err, res) {
      if (err) {
        return reject(err);
      }
      return resolve(res);
    });
  });
}

co(function *() {
  const resA = read('./a.js');
  const resB = read('./b.js');
  const resC = read('./c.js');

  return yield {a: resA, b: resB, c: resC};
}).then(function(res){
  console.log(res);
  // {
  //    a: a file content, 
  //    b: b file content, 
  //    c: c file content
  // }
});

4.将generator做回返回值

'use strict';
const fs = require('fs');
const co =require('co');

function* read(filename) {
  return yield function(fn) {
    return fs.readFile(filename, 'utf8', fn);
  }
}

co(function *() {
  return yield read('./a.js');
}).then(function(res){
  console.log(res);
})

源码分析

promise

由使用方法可以看出来,co接受一个generator函数作为参数(以下简称G函数),并自动化地执行异步操作后返回promise对象。yield值可以是一个generatorpromise,数组,对象或函数。

我们先来看co函数。var args = slice.call(arguments, 1); 收集除了第一个参数外的所有参数。接着co返回一个Promise对象,在这个对象内,第一步先判断是否为一个函数,G函数当然也算是函数,于是便将参数applyG函数。接着调用onFulfilled函数。onFulfilled函数和onRejected函数就相当于是这个大Promise对象resolverejectonFulfilled先执行一次next,随后进入next函数。

为了便于分析,我们现在先假定是上面的第一种情况,也就是yield的值是一个promise。当在上一步执行完G函数自带next后,会得到一个形如{ value: Promise { <pending> }, done: false }的对象,也就是read函数return出来的处于pending状态的promise对象。将这个对象传给next函数,next函数先判断是否已完成(done),若未完成,则继续执行。接着调用toPromise函数,将value值转成一个promise,可以看到该方法对promise对象,generator函数,普通函数,对象和数组都做了相应的封装将其转化为promise

接下来看下一行。紧接着这个promise被调用了then方法,而resolvereject正是onFulfilledonRejected。这就形成了一个迭代,直至yielddone值为true时,这个迭代就结束了,返回resolvereject,被co外的then方法取得。

array和object

看完了promise是如何工作的,再来看arrayobject。其实这两个都差不多,array是通过Promise.all被调用,而object是通过调用objectToPromise方法包装成一个数组,最后仍然通过Promise.all被调用。

arrayToPromise方法调用了Promise.all方法,Promise.all方法用于将多个promise实例重新包装成一个。

'use strict';
let promise1 = Promise.resolve(1);
let promise2 = Promise.resolve(2);

Promise.all([promise1, promise2])
        .then(val => {
            console.log(val); // [1, 2]
        });

arrayToPromise做的就是then前面这一步。

objectToPromise首先通过new obj.constructor()来构造一个和传入对象有相同构造器的对象,results对象里会放Promise.all之后的结果,其中是promise对象的通过defer函数取得resolve之后的结果,不是promise对象的,如布尔值,常量等,原样返回。我们来看一下defer函数,这个函数会给每个传递来的promise对象绑定resolve方法(也就是then的第一个参数),而有些resolve是没有返回值的,所以就先将对应key的值置为undefined。最后,通过Promise.allresults对象中的promise对象都通过绑定的resolve对象变成了执行后的结果。

generator

thunkToPromise就不讲了,因为在co@4.0之前是通过thunk实现,之后都转成用promise实现,tj也似乎想放弃这一实现,只是为了向前兼容而写了个thunkToPromise,毕竟马上就要有async/await了。根据tj在readme里写的,co是一块迈向async/await的垫脚石。

co.wrap

最后讲一讲co.wrapco.wrap的作用是将一个generator函数转成一般的函数以方便调用。先看一个实例。

co(function *() {
  return yield read('./a.js');
}).then(function(res){
  console.log(res);
})

这个函数是一次性的,不能被复用,若想在别处使用,只能再复制一次,这显然是不能忍的,于是就有了co.wrap

var readFile = co.wrap(function *(filename) {
  return yield read(filename);
});

readFile('./a.js').then(function(res) {
  console.log(res);
});

readFile('./b.js').then(function(res) {
  console.log(res);
});

看一下源码,只有短短的几行,功能也很简单,就是在原来的generator函数外包了一层函数用来接收参数,再将这些参数一并置入co中。这个外部函数一般被称为高阶函数或偏函数,推荐阅读兔哥的这篇文章

@qianlongo
Copy link

是onFulfilled和onRejected。这就形成了一个迭代,直至yield的done值为false(这里应该是为true时)时,这个迭代就结束了,返回resolve或reject,被co外的then方法取得。

@brunoyang
Copy link
Owner Author

@qianlongo 谢谢指正 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants