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 条评论
登录 后参与评论

相关文章

来自专栏大内老A

WCF服务端运行时架构体系详解[下篇]

作为WCF中一个核心概念,终结点在不同的语境中实际上指代不同的对象。站在服务描述的角度,我们所说的终结点实际上是指ServiceEndpoint对象。如果站在W...

1637
来自专栏大内老A

WCF服务端运行时架构体系详解[中篇]

在这篇文章中,我们对信道分发器本身作一个深入的了解,首先来看看它具有哪些可供扩展的组件,以及我们可以针对信道分发器对WCF实现哪些可能的扩展。 目录: ...

18210
来自专栏IMWeb前端团队

co.js 异步回调的原理

本文作者:IMWeb 何方舟 原文出处:IMWeb社区 未经同意,禁止转载 co.js 作为 koa 框架的核心库,利用 es6 Generator ...

2018
来自专栏7号代码

Android应用性能优化——内存优化(内附一个内存泄露优化实例)

自动管理内存和回收机制,垃圾回收器负责回收程序中已经不使用,但是仍然被各种对象占用的内存,将程序员从繁重、危险的内存管理工中解放出来。

1021
来自专栏前端小吉米

async/await 一种非常丝滑的异步语法

1443
来自专栏互联网杂技

前端异步代码解决方案实践(一)

小程序框架提供丰富的原生API,可以方便调起微信提供的能力,如获取用户信息,本地存储,支付功能等。但大多数API为异步调用,需要传递成功或失败回调函数,例如wx...

1073
来自专栏较真的前端

探寻Vue数据双向绑定的底层原理

1815
来自专栏进击的君君的前端之路

ReactJS简介

1133
来自专栏Linyb极客之路

软件开发中的原则

一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。(Every object should have a single responsibilit...

645
来自专栏hrscy

202 - Swift 的核心是什么?

不知道大家有没有看过 WWDC 2015 的视频,其中有一个编号为 408 的视频解释了这个问题,下面是视频链接:Protocol-Oriented Progr...

982

扫码关注云+社区