Thunck函数的含义

编译器的传名调用实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。

function f(m){
    return m*2;
}
f(x+5);
//等同于
var thunk = function(){
    return x+5;
};
function f(thunk){
    return thunk()*2;
}

上面代码中,函数f的参数x+5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。
这就是thunk函数的定义,它是传名调用的一种实现策略,用来替换某个表达式。

JavaScript语言的Thunk函数

JavaScript语言是传值调用,它的Thunck函数含义有所不同。在JavaScript语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。

//正常版本的readFile(多参数版本)
fs.readFile(fileName,callback);
//Thunk版本的readFile(单参数版本);
var Thunk = function(fileName){
    return function(callback){
        return fs.readFile(fileName,callback);
    };
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);

上面代码中,fs模块的readFile方法是一个错参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个但参数函数,只接受回调函数作为参数。这个单参数版本,就叫做Thunk函数。
任何函数,只要参数有回调函数,就能写成Thunk函数的形式。下面是一个简单的Thunk函数转换器。

//ES5版本
var Thunk = function(fn){
    return function(){
        var args = Array.Prototype.slice.call(arguments);
        return function(callback){
            args.push(callback);
            return fn.apply(this,args);
        }
    };
};
//ES6版本
const Thunk = function(fn){
    return function(...args){
        return function(callback){
            return fn.call(this,...args,callback);
        }
    };
};
//使用上面的转化器,生生fs.readFile的Thunk函数。
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback)

Thunkify模块

生产环境的转换器,建议使用Thunkify模块。
首先是安装。

$ npm install thunkify

使用方式如下。

var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
read('package.json')(function(err,str){
    //...
})

Thunkify的源码与上面的简单的转换器非常像。

function thunkfiy(fn){
    return function(){
        var args = new Array(arguments.length);
        var ctx = this;
        for(var i=0;i<args.length;++i){
            args[i] = arguments[i];
        }
        return function(done){
            var called;
            args.push(function(){
                if(called) return;
                called = true;
                done.apply(null,arguments);
            });
            try{
                fn.apply(ctx,args);
            }catch(err){
                done(err);
            }
        }
    }
}

它的源码主要多了一个检查机制,变量called确保回调函数只运行一次,请看下面的例子。

function f(a,b,callback){
    var sum = a+b;
    callback(sum);
    callback(sum);
}
var ft = thunkify(f);
var print = console.log.bind(console);
ft(1,2)(print);
//上面代码中,由于thunkify只允许回调函数执行一次,所以只输出一行结果。

Lessong
101 声望4 粉丝