资讯专栏INFORMATION COLUMN

理解thunk函数的作用及co的实现

张巨伟 / 2701人阅读

摘要:从形式上将函数的执行部分和回调部分分开,这样我们就可以在一个地方执行执行函数,在另一个地方执行回调函数。这样做的价值就在于,在做异步操作的时候,我们只需要知道回调函数执行的顺序和嵌套关系,就能按顺序取得执行函数的结果。

thunk

thunk 从形式上将函数的执行部分回调部分分开,这样我们就可以在一个地方执行执行函数,在另一个地方执行回调函数。这样做的价值就在于,在做异步操作的时候,我们只需要知道回调函数执行的顺序和嵌套关系,就能按顺序取得执行函数的结果。

以下是 thunk 的简单实现:

function thunkify (fn) {
    return function () {
        var args = Array.prototype.slice(arguments);
        var ctx = this;

        return function (done) {
            var called = false;
            args.push(function() {
                if (called) return;
                called = true;
                done.apply(null, arguments);
            });
            try {
                fn.apply(ctx, args);
            } catch (err) {
                done(err);
            }
        }
    }
}

上面的实现将函数原有的执行变为按“执行部分”和“回调部分”分别执行的方式:

fn(a, callback) => thunkify(fn)(a)(callback)

例如:

var fs = require("fs");
var readFile = thunkify(fs.readFile); // 将readFile函数包进thunkify,变为thunkify函数

//**这是执行函数集合**//
var f1 = readFile("./a.js");
var f2 = readFile("./b.js");
var f3 = readFile("./c.js");

//**这是回调函数集合**//
//利用嵌套控制f1 f2执行的顺序
f1(function(err, data1) {
    // doSomething
    f2(function(err, data2) {
        // doSomething
        f3(function (err, data3) {
            // doSomething
        })
    })
})

而传统的写法为:

//传统写法
fs.readFile("./a.js", function(err, data1) {
    // doSomething
    fs.readFile("./b.js", function(err, data2) {
        // doSomething
        fs.readFile("./c.js", function(err, data3) {
            // doSomething
        })
    })
})

在执行部分和回调部分分开之后,就可以使用generator等异步控制技术方便地进行流程控制,避免回调黑洞。上述的文件读取流程就可以用generator进行改造:

var fs = require("fs");
var readFile = thunkify(fs.readFile);

//**函数的‘执行部分’放在一起执行**//
var gen = function* () {
    var data1 = yield readFile("./a.js");
    // 用户获取数据后自定义写在这里
    console.log(data1.toString());
    
    var data2 = yield readFile("./b.js");
    // 用户获取数据后自定义写在这里
    console.log(data2);
    ····
}

// 函数的‘回调部分’在另一个地方执行,且调用的形式都一样
var g = gen();
var d1 = g.next(); // 返回的结果为{value: func, done: boolean}

// 执行value,实际为执行`d1.value(callback)`
// 也即`thunkify(fs.readFile)("./a.js")(callback)`
d1.value(function(err, data) {
    if (err) throw err;
    // g.next(data) 可以将参数data传回generator函数体,作为上一个阶段异步任务的执行结果
    // 例子中,data被传回了gen函数体,作为data1的值
    var d2 = g.next(data);
    d2.value(function(err, data2) {
        if (err) throw err;
        g.next(data2);
    });
});
co

在上述的改造中发现,执行回调部分的时候,依旧存在回调嵌套:d2.valued1.value的回调中执行。观察后发现,其实在执行回调的时候,也就是g在执行next()的时候,执行的形式基本相同,都是:

d.value(function(err, data) {
    if (err) throw err;
    g.next(data);
});

这种形式,所以可以通过编写一个递归函数来整理流程。

function run(fn) {
    var g = fn();
    
    // 下一步控制函数,实际就是d.value的回调函数
    function next(err, data) {
        // 把前面一个数据给传递到gen()函数里面
        var result = g.next(data);
        // 判断是否结束
        if (result.done) return;
        // 下一句执行回调next的时候 不断的递归
        result.value(next);
    }
    // 执行第一步
    next();
}

// 使用
run(gen);

上面代码中的过程很好理解,就是把gen放到一个递归器中去执行,在这个递归器中有一个核心的函数next,这个函数就是递归函数。当函数中的g.next(data)返回的done属性值为true,就表示当前生成器函数中的yield已经执行完毕,退出就OK。当不为true,表示当前生成器函数还有未执行的yield,于是继续调用next函数继续执行同样的流程。

而上述的流程就是异步流控制库co的简单实现。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90633.html

相关文章

  • co模块用法分析

    摘要:模块可以将异步解放成同步。源码分析使用的模块版本号为首先看一些用于判断对象类型的函数对数组方法的引用这两个应该就不用说了吧。。。看一下模块的输出部分因此以下三种用法等价接着就是重头戏函数了。 本文只在个人博客和 SegmentFault 社区个人专栏发表,转载请注明出处 个人博客: https://zengxiaotao.github.io SegmentFault 个人专栏: h...

    muzhuyu 评论0 收藏0
  • Node.js 异步异闻录

    摘要:的异步完成整个异步环节的有事件循环观察者请求对象以及线程池。执行回调组装好请求对象送入线程池等待执行,实际上是完成了异步的第一部分,回调通知是第二部分。异步编程是首个将异步大规模带到应用层面的平台。 showImg(https://segmentfault.com/img/remote/1460000011303472); 本文首发在个人博客:http://muyunyun.cn/po...

    zzbo 评论0 收藏0
  • JavaScript 异步队列Co实现

    摘要:在中,又由于单线程的原因,异步编程又是非常重要的。方法有很多,,,观察者,,,这些中处理异步编程的,都可以做到这种串行的需求。 引入 队列对于任何语言来说都是重要的,io 的串行,请求的并行等等。在 JavaScript 中,又由于单线程的原因,异步编程又是非常重要的。昨天由一道面试题的启发,我去实现 JS 中的异步队列的时候,借鉴了 express 中间件思想,并发散到 co 实现 ...

    LdhAndroid 评论0 收藏0
  • co-parallel & co-gather源码解析

    摘要:昨天也是好好的看了一下的源码,今天打算自己来做一下解析。源码如下这段代码真的是很短,但是方法真的很巧妙。因为两个方法用到了,这里把的源码也贴出来源码的描述就是为了执行而创建的。最后再次感谢提供的思路。 原文链接,转载请注明出处 最近看了Ma63d关于爬虫的这篇文章,正好自己也在做爬虫,看到他在文中提到了co-parallel和co-gather,就打算改一下自己的代码(本来代码就只是为...

    caozhijian 评论0 收藏0
  • 《Node.js设计模式》基于ES2015+回调控制流

    摘要:以下展示它是如何工作的函数使用构造函数创建一个新的对象,并立即将其返回给调用者。在传递给构造函数的函数中,我们确保传递给,这是一个特殊的回调函数。 本系列文章为《Node.js Design Patterns Second Edition》的原文翻译和读书笔记,在GitHub连载更新,同步翻译版链接。 欢迎关注我的专栏,之后的博文将在专栏同步: Encounter的掘金专栏 知乎专栏...

    LiuRhoRamen 评论0 收藏0

发表评论

0条评论

张巨伟

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<