co.js 异步回调的原理

本文作者:IMWeb 何方舟 原文出处:IMWeb社区 未经同意,禁止转载

co.js 作为 koa 框架的核心库,利用 es6 Generator 新特性来解决 callback hell 已经非常流行 。 本文将剖析 co.js 是为何用同步的写法,就可以解决异步回调的问题。

Generator

首先简要介绍一下 Generator 特性, co.js 是基于该特性实现的,所以弄清 Generator 的远离非常重要。

function* fn(){
    beforeA();
    yield doA();
    yield doB();
    afterB();
}
var gen = fn(); //生成构造器;
gen.next(); //这里会执行到以第一个yield之前的位置,所以执行beforeA 和 doA 这两行;
gen.next(); //这里会执行到第二个yield的位置,也就是执行 doB() 
gen.next(); //这里会执行到生成器结束的位置,afterB();

简单来说 generator 可以变成一种分步函数,gen 成为这 Generator 函数的指针,通过调用 gen.next() 来执行下一步,这也是异步执行的关键。generator详细介绍请看这里 是不是有种感觉可以利用这个next来达到异步的,但是好像又不知道怎么该怎么去做,那先看看下面这个例子。

var fs = require("fs");
fs.readFile('path1', function (err, data) {
  if (err) throw err;
  console.log(data);
  fs.readFile('path2', function (err, data) {
    if (err) throw err;
        console.log(data);
  });
});

这是一个常见的异步回调的例子,现在我们用generator来改写它,下面是第一版。

var fs = require("fs");

function* unname(){

    var data1 = yield fs.readFile('path1',function(err,data){
        if(err) gen.throw(err);
        gen.next(data);
    });

    console.log(data1);

    var data2 = yield fs.readFile('path2',function(err,data){
        if(err) gen.throw(err);
        gen.next(data);

    }); 

    console.log(data2);
}

var gen = unname();

大功告成!可是好像哪里不对,这个本质上还是之前的回调方法。我们期望的方法应该是类似这样的,通过一个yield关键字,来表明这里是异步执行的。这样的写法简洁明了,但直接这样写肯定是不能执行的。

var fs = require("fs");

function* unname(){
    var data1 = yield fs.readFile('path1');
    console.log(data1);
    var data2 = yield fs.readFile('path2');
    console.log(data2);
}

为了达到这个目的,我们必须借助其他工具函数,这个就是Co。

var fs = require("fs");

co(function*(){
    var data1 = yield readFile('path1');
    console.log(data1);
    var data2 = yield readFile('path2');
    console.log(data2);
});

function readFile( path ){
    return function(callback){
       fs.readFile( path , callback);
    }
}

function co( fn ) {

    var gen = fn();
    function next(err,data){   
        var result = gen.next(data);
        if(!result.done){           
            result.value(next); 
        }
    }
    next();

}

上面的代码有两个关键点,一个是 readfile 函数的 thunk 化,还有就是 co 函数了,这里是最简单的实现。 网上很多教程都忽略了这一点,就是 Co 中需要流程控制的函数,都必须要 Thunk 化或者 Promise 化。因为 Promise 相对于 Thunk 要复杂一点,这里只介绍 Thunk 化。

所谓 Thunk 化就是将多参数函数,将其替换成单参数只接受回调函数作为唯一参数的版本 ,上面代码中的 readFile 就是个例子。 原生的api是不支持 thunk 化的,所以就有了thunkify这个库帮我们把一些原生 api thunk 化。

为什么要 thunk 化呢?由之前的分析我们可以知道,利用 generator 来实现异步回调的实质就是把, gen.next() 放入回调函数中, thunk 化之后,可以得到一个只接受 callback 的函数,换句话说,函数中除了 callback 其他都参数都已经传入了,callback 里的内容就可以交给 Co 去决定!

现在让我们来看下 Co 里面的代码。第一次执行 gen.next() 返回的 result.value 就是 fs.readFile thunk 化后的函数,就是这样的一个函数

function(callback){   
    fs.readFile( 'path1',callback );
}

通过result.value(next)中,我们就可以在callback中执行 gen.next(),翻译过来是这样。

function(callback){
    fs.readFile( 'path1', next );
}

这样就达到了我们想要异步执行的效果!

上面代码中的 Co 和 thunk 都是最简单的实现方式,代码中缺少诸如异常处理,非标准参数,多参数回调等判断,可以参考一下 Co 和 thunkify ,来实现。在 Co 的4.XX版本之后,内部的机制全部改为用 Promise 的实现,虽然看上去 Promise 是大势所趋,但是个人来说还是更喜欢Thunk的方式。等更深入学习 Promise 之后,会介绍 Promise 的实现方式。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

Java IO 之 管道流 原理分析

47910
来自专栏Java架构师学习

带你深入了解Java线程中的那些事

引言 说到Thread大家都很熟悉,我们平常写并发代码的时候都会接触到,那么我们来看看下面这段代码是如何初始化以及执行的呢? public class Thre...

3248
来自专栏王二麻子IT技术交流园地

Java--Socket通信(双向)

新建两个工程,一个客户端,一个服务端,先启动服务端再启动客户端 两个工程的读写操作线程类基本上完全相同 服务端: import java.io.Buffered...

2825
来自专栏MasiMaro 的技术博文

PE文件详解(六)

这篇文章转载自小甲鱼的PE文件详解系列原文传送门 之前简单提了一下节表和数据目录表,那么他们有什么区别? 其实这些东西都是人为规定的,一个数据在文件中或...

2252
来自专栏SDNLAB

Open vSwitch系列之openflow版本兼容

众所周知Open vSwitch支持的openflow版本从1.0到1.5版本(当前Open vSwitch版本是2.3.2)通过阅读代码,处理openflow...

55513
来自专栏函数式编程语言及工具

泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process

  在上两篇讨论中我们介绍了IO Process:Process[I,O],它的工作原理、函数组合等。很容易想象,一个完整的IO程序是由 数据源+处理过程+数据...

2285
来自专栏一枝花算不算浪漫

[Java 缓存] Java Cache之 Guava Cache的简单应用.

3906
来自专栏Java3y

ConcurrentHashMap基于JDK1.8源码剖析

2578
来自专栏Elasticsearch实验室

Elasitcsearch 底层系列 Lucene 内核解析之 Doc Value

       Elasticsearch 支持行存和列存,行存用于以文档为单位顺序存储多个文档的原始内容,在 Elasitcsearch 底层系列 Lucene...

3355
来自专栏大内老A

IoC+AOP的简单实现

对EnterLib有所了解的人应该知道,其中有一个名叫Policy Injection的AOP框架;而整个EnterLib完全建立在另一个叫作Unity的底层框...

2089

扫码关注云+社区

领取腾讯云代金券