Thunk函数的使用

Thunk函数的使用

编译器的求值策略一般分为传值调用以及传名调用,Thunk函数是应用于编译器的传名调用实现,每每是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫作Thunk 函数。javascript

求值策略

编译器的求值策略一般分为传值调用以及传名调用,在下面的例子中,将一个表达式做为参数进行传递,传值调用以及传名调用中实现的方式有所不一样。html

var x = 1;

function s(y){
    console.log(y + 1); // 3
}

s(x + 1);

在上述的例子中,不管是使用传值调用仍是使用传名调用,执行的结果都是同样的,可是其调用过程不一样:java

  • 传值调用:首先计算x + 1,而后将计算结果2传递到s函数,即至关于调用s(2)
  • 传名调用:直接将x + 1表达式传递给y,使用时再计算x + 1,即至关于计算(x + 1) + 1

传值调用与传名调用各有利弊,传值调用比较简单,可是对参数求值的时候,实际上还没用到这个参数,有可能形成没有必要的计算。传名调用能够解决这个问题,可是实现相对来讲比较复杂。git

var x = 1;

function s(y){
    console.log(y + 1); // 3
}

s(x + 1, x + 2);

在上面这个例子中,函数s并无用到x + 2这个表达式求得的值,使用传名调用的话只将表达式传入而并未计算,只要在函数中没有用到x + 2这个表达式就不会计算,使用传值调用的话就会首先将x + 2的值计算而后传入,若是没有用到这个值,那么就多了一次没有必要的计算。Thunk函数就是做为传名调用的实现而构建的,每每是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫作Thunk 函数。github

var x = 1;

function s(y){
    console.log(y + 1); // 3
}

s(x + 1);

// 等同于

var x = 1;

function s(thunk){
    console.log(thunk() + 1); // 3
}

var thunk = function(){
    return x + 1;
}

s(thunk);

Js中的Thunk函数

Js中的求值策略是是传值调用,在Js中使用Thunk函数须要手动进行实现且含义有所不一样,在Js中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数做为参数。segmentfault

// 假设一个延时函数须要传递一些参数
// 一般使用的版本以下
var delayAsync = function(time, callback, ...args){
    setTimeout(() => callback(...args), time);
}

var callback = function(x, y, z){
    console.log(x, y, z);
}

delayAsync(1000, callback, 1, 2, 3);

// 使用Thunk函数

var thunk = function(time, ...args){
    return function(callback){
        setTimeout(() => callback(...args), time);
    }
}

var callback = function(x, y, z){
    console.log(x, y, z);
}

var delayAsyncThunk = thunk(1000, 1, 2, 3);
delayAsyncThunk(callback);

实现一个简单的Thunk函数转换器,对于任何函数,只要参数有回调函数,就能写成Thunk函数的形式。网络

var convertToThunk = function(funct){
  return function (...args){
    return function (callback){
      return funct.apply(this, args);
    }
  };
};

var callback = function(x, y, z){
    console.log(x, y, z);
}

var delayAsyncThunk = convertToThunk(function(time, ...args){
    setTimeout(() => callback(...args), time);
});

thunkFunct = delayAsyncThunk(1000, 1, 2, 3);
thunkFunct(callback);

Thunk函数在ES6以前可能应用比较少,可是在ES6以后,出现了Generator函数,经过使用Thunk函数就能够能够用于Generator函数的自动流程管理。首先是关于Generator函数的基本使用,调用一个生成器函数并不会立刻执行它里面的语句,而是返回一个这个生成器的迭代器iterator 对象,他是一个指向内部状态对象的指针。当这个迭代器的next()方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield后紧跟迭代器要返回的值,也就是指针就会从函数头部或者上一次停下来的地方开始执行到下一个yield。或者若是用的是yield*,则表示将执行权移交给另外一个生成器函数(当前生成器暂停执行)。app

function* f(x) {
    yield x + 10;
    yield x + 20;
    return x + 30;
}
var g = f(1);
console.log(g); // f {<suspended>}
console.log(g.next()); // {value: 11, done: false}
console.log(g.next()); // {value: 21, done: false}
console.log(g.next()); // {value: 31, done: true}
console.log(g.next()); // {value: undefined, done: true} // 能够无限next(),可是value总为undefined,done总为true

因为Generator函数可以将函数的执行暂时挂起,那么他就彻底能够操做一个异步任务,当上一个任务完成以后再继续下一个任务,下面这个例子就是将一个异步任务同步化表达,当上一个延时定时器完成以后才会进行下一个定时器任务,能够经过这种方式解决一个异步嵌套的问题,例如利用回调的方式须要在一个网络请求以后加入一次回调进行下一次请求,很容易形成回调地狱,而经过Generator函数就能够解决这个问题,事实上async/await就是利用的Generator函数以及Promise实现的异步解决方案。dom

var it = null;

function f(){
    var rand = Math.random() * 2;
    setTimeout(function(){
        if(it) it.next(rand);
    },1000)
}

function* g(){ 
    var r1 = yield f();
    console.log(r1);
    var r2 = yield f();
    console.log(r2);
    var r3 = yield f();
    console.log(r3);
}

it = g();
it.next();

虽然上边的例子可以自动执行,可是不够方便,如今实现一个Thunk函数的自动流程管理,其自动帮咱们进行回调函数的处理,只须要在Thunk函数中传递一些函数执行所须要的参数好比例子中的index,而后就能够编写Generator函数的函数体,经过左边的变量接收Thunk函数中funct执行的参数,在使用Thunk函数进行自动流程管理时,必须保证yield后是一个Thunk函数。
关于自动流程管理run函数,首先须要知道在调用next()方法时,若是传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量,在这个函数中,第一次执行next时并未传递参数,并且在第一个yield上边也并不存在接收变量的语句,无需传递参数,接下来就是判断是否执行完这个生成器函数,在这里并无执行完,那么将自定义的next函数传入res.value中,这里须要注意res.value是一个函数,能够在下边的例子中将注释的那一行执行,而后就能够看到这个值是f(funct){...},此时咱们将自定义的next函数传递后,就将next的执行权限交予了f这个函数,在这个函数执行完异步任务后,会执行回调函数,在这个回调函数中会触发生成器的下一个next方法,而且这个next方法是传递了参数的,上文提到传入参数后会将其传递给上一条执行的yield语句左边的变量,那么在这一次执行中会将这个参数值传递给r1,而后在继续执行next,不断往复,直到生成器函数结束运行,这样就实现了流程的自动管理。异步

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct({rand:rand, index: index}), 1000)
    }
}

function* g(){ 
    var r1 = yield thunkFunct(1);
    console.log(r1.index, r1.rand);
    var r2 = yield thunkFunct(2);
    console.log(r2.index, r2.rand);
    var r3 = yield thunkFunct(3);
    console.log(r3.index, r3.rand);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }

    next();
}

run(g);

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/9302a1d01113
https://segmentfault.com/a/1190000017211798
http://www.ruanyifeng.com/blog/2015/05/thunk.html
相关文章
相关标签/搜索