co.js 异步回调的原理

作者:何方舟

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 的实现方式。

原文链接:http://ivweb.io/topic/5774a6b9af96c5e776f1f5c8

原文链接:

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏喔家ArchiSelf

全栈必备 Java基础

那一年,从北邮毕业,同一年,在大洋的彼岸诞生了一门对软件业将产生重大影响的编程语言,它就是——Java。1998年的时候,开始学习Java1.2,并在Java ...

684
来自专栏Golang语言社区

GO语言并发编程之互斥锁、读写锁详解

在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有提到过读写锁。这两种锁对于传统的并发程序来说都...

2665
来自专栏鸿的学习笔记

python的协程

yield指令有两个功能:yield item用于产出一个值,反馈给next()的调用方。

662
来自专栏Golang语言社区

GO语言并发编程之互斥锁、读写锁详解

在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有提到过读写锁。这两种锁对于传统的并发程序来说都...

3397
来自专栏林冠宏的技术文章

如何用 ajax 连接mysql数据库,并且获取从中返回的数据。ajax获取从mysql返回的数据。responseXML分别输出不同数据的方法。

      开讲前,先说下网上,大部分的关于这方面的博文或者其他什么的,就我自己的感觉,第一说得不详细,第二语言不能很好的被初学者了解。 我这篇的标题之所以用了...

2207
来自专栏大内老A

WCF技术剖析之十六:数据契约的等效性和版本控制

数据契约是对用于交换的数据结构的描述,是数据序列化和反序列化的依据。在一个WCF应用中,客户端和服务端必须通过等效的数据契约方能进行有效的数据交换。随着时间的推...

1669
来自专栏Golang语言社区

GO语言并发编程之互斥锁、读写锁详解

在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有提到过读写锁。这两种锁对于传统的并发程序来说都...

34715
来自专栏me的随笔

.NET中数据访问方式(一):LINQ

语言集成查询(Language-Integrated Query),简称LINQ,.NET中的LINQ体系如下图所示:

753
来自专栏微信公众号:Java团长

《深入理解Java虚拟机》笔记

也就是说,我们完全可以做一个工具,从一个文件中读入指令,然后将这些指令运行起来。上面代码中“编好的机器指令”当然指的是能在CPU上运行的,如果这里我还实现了一个...

461
来自专栏Golang语言社区

GO语言并发编程之互斥锁、读写锁详解

在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有提到过读写锁。这两种锁对于传统的并发程序来说都...

3714

扫码关注云+社区