Thunk函数的使用

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

求值策略

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

var x = 1;

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

在上述的例子中,无论是使用传值调用还是使用传名调用,执行的结果都是一样的,但是其调用过程不同:

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

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

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 函数。

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函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

// 假设一个延时函数需要传递一些参数
// 通常使用的版本如下
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*,则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

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实现的异步解决方案。

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

Thunk函数的使用的更多相关文章

  1. JavaScript 中的 Thunk 函数

    参数的求值策略: var x = 1; function f(m){ return m * 2; } f(x + 5); // x +5 在何时运算? 1.传值调用: var x = 1; funct ...

  2. 转: ES6异步编程:Thunk函数的含义与用法

    转: ES6异步编程:Thunk函数的含义与用法 参数的求值策略 Thunk函数早在上个世纪60年代就诞生了. 那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好.一个争论的焦点是&quo ...

  3. js-学习笔记-Thunk函数

    Thunk 函数是自动执行 Generator 函数的一种方法. 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体.这个临时函数就叫做 Thunk 函数. fun ...

  4. thunk函数

    1.函数参数求值的策略 a.传值策略(c语言) 传值策略就是在进入函数体之前将 参数计算之后 将参数的值传入到函数体之中. let x = 8 f(x + 1)//参数为 f(9)//传进去的值实际上 ...

  5. thunk 函数

    function* f() { console.log(1); for (var i = 0; true; i++) { console.log('come in'); var reset = yie ...

  6. async 函数学习笔记

    async函数就是Generator函数的语法糖. var fs = require('fs'); var readFile = function (fileName) { return new Pr ...

  7. 转: ES6异步编程: co函数库的含义与用法

    转: ES6异步编程: co函数库的含义与用法 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行. 比如,有一个 Ge ...

  8. ECMAScript 6 学习(二)async函数

     1.什么是async函数 2.用法 2.1基本用法 3.语法 3.1返回promise对象 3.2promise状态的变化 3.3await命令 1.什么是async函数 async函数也是异步编程 ...

  9. ES2017中的async函数

    前面的话 ES2017标准引入了 async 函数,使得异步操作变得更加方便.本文将详细介绍async函数 概述 async 函数是 Generator 函数的语法糖 使用Generator 函数,依 ...

随机推荐

  1. web自动化之键盘操作

    简单介绍下web自动化怎么触发键盘操作 按键操作 需要导入的类from selenium.webdriver.common.keys import Keys 组合键Keys.CONTROL 也就是我们 ...

  2. [计划任务 - dos]制作任务工具

    语法 schtasks /create /tn TaskName /tr TaskRun /sc schedule [/mo modifier] [/d day] [/m month[,month.. ...

  3. [PHP学习教程 - 心得]001.偷龙转凤技巧10则(Remember Tips)

    引言:PHP当中的一些猬锁技巧,比较基础,想起就发贴总结一下,老鸟换个姿势飘过去就是. [技巧]应该属于“方法”的一个范畴,主要指对一种生活或工作方法的熟练和灵活运用.[五笔]RFAG. 话不多说,下 ...

  4. 在线编写复杂的数学公式--EdrawMath

    网址: EdrawMath , 非常好用

  5. 在 MacOS 中使用 multipass 安装 microk8s 环境

    在 MacOS 中使用 multipass 安装 microk8s 环境 Multipass & MicroK8s 介绍 What is Kubernetes? Kubernetes clus ...

  6. axios发送post form请求

    axios发送post form请求 只需修改url和data即可 axios({ url: '/user', method: 'post', data: { firstName: 'Fred', l ...

  7. thymeleaf怎么在页面上面格式化时间

    th:value="${#dates.format(后端传递的时间,‘yyyy-MM-dd HH:mm:ss’)}"

  8. URL跳转与钓鱼

    从登录页跳转到另一个页面就叫做URL跳转. 1.URL跳转 URL跳转一般分为两种,(1)客户端跳转:(2)服务端跳转.对用户来说,两种跳转都是透明的,都是指向或者跳转到另一个页面,页面发生了改变.但 ...

  9. Java实现 LeetCode 684 冗余连接(并查集)

    684. 冗余连接 在本问题中, 树指的是一个连通且无环的无向图. 输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, -, N) 的树及一条附加的边构成.附加的边的两个顶点包含在1到N中间 ...

  10. Java实现 LeetCode 187 重复的DNA序列

    187. 重复的DNA序列 所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:"ACGAATTCCG".在研究 DNA 时,识别 DNA 中的重复序列有时会对 ...