JS中的thunk函数
说起thunk,我们先来看一个简易 thunk 结合 ajax 的例子
const thunkify = function (fn) {
return function (url) {
return function (callback) {
return fn(url, callback)
}
}
}
function ajax(url, cb) {
$.ajax({
url: url,
success: cb
});
}
const getData = thunkify(ajax)
const req1 = getData('https://jsonplaceholder.typicode.com/todos/1')
req1(function (data) {
console.log(data)
}) // req1 接收一个回调函数为参数
这时候就有小伙伴要说了,这不是 curry
吗?怎么能叫 thunk
?区别在于解决的问题不同,首先 两者都是符合函数式编程理念的, thunk
是延迟计算,他将多参数改为单参数版本,且只接收回调函数,或是最后一个参数必需是回调函数,而 curry
却无该限制。thunk
更多是想把执行函数和回调函数拆分开,让开发者在写异步代码的时候可以更专注于执行函数的逻辑。
Thunkify 模块
建议使用更完善的 thunkify 转换器生成 thunk 函数 Thunkify 模块
使用方法
const ajaxThunk = thunkify(ajax);
ajaxThunk('https://jsonplaceholder.typicode.com/todos/1')(foo)
源码
function thunkify(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);
}
}
}
};
自定义逻辑怎么办
还是以ajax
为例:
function ajax(url, cb) {
$.ajax({
url: url,
success: cb
});
}
// --------------thunk写法--------------
var getData = thunkify(ajax);
// 执行函数
var req1 = getData("https://jsonplaceholder.typicode.com/todos/1");
var req2 = getData("https://jsonplaceholder.typicode.com/todos/2");
var req3 = getData("https://jsonplaceholder.typicode.com/todos/3");
// 回调函数
req1(function(data1) {
req2(function(data2) {
req3(function (data3) {
})
})
})
// --------------thunk写法--------------
// ---------------传统写法-----------------
ajax('./api/1', function(data1) {
//传统定义的逻辑一般写在回调里
ajax('./api/2', function(data2) {
ajax('./api/3', function(data3) {
})
})
})
// ---------------传统写法-----------------
根据 thunk
的理念我们将执行函数
和回调函数
分开以后,代码清晰了许多。 但是问题来了 自定义的逻辑 应该放在哪?
如果自定义的逻辑是放在回调函数
集合那边,嵌套逻辑处理太多的话,那thunk的优势就没了,且可读性大大降低,那我们就把自定义的逻辑放在执行函数的一端,回调函数只是负责获取数据,并将参数传回执行函数集合
所以现在流程就是
执行函数-->等待回调函数返回数据-->对数据进行自定义操作
等待传回数据不就是 Gennerator 函数吗?Thunk 函数确实没什么卵用,但真正让其发挥作用的是配合Generator函数实现自动化异步操作。
如何结合
把所有的执行函数放入generator
函数里面,利用generator
函数的yield
对执行函数的流程控制再把函数执行权移出函数到对应的回调函数,获取数据后再把数据返回来。
还是以ajax
请求为例
我们利用 thunk
将 执行 与 回调 拆分开 从而变成执行函数放在一起 回调函数放在一起, 并且使用 yield
将两者进行连接
import $ from "jquery";
import thunkify from "thunkify";
function ajax(url, cb) {
$.ajax({
url: url,
success: cb
});
}
const getData = thunkify(ajax);
// 创建 Generator
const gen = function* () {
const data1 = yield getData("https://jsonplaceholder.typicode.com/todos/1");
console.log(data1); // 获取数据后自定义操作
const data2 = yield getData("https://jsonplaceholder.typicode.com/todos/2");
console.log(data2);
const data3 = yield getData("https://jsonplaceholder.typicode.com/todos/3");
console.log(data3);
};
// 手动执行 Generator
var g = gen();
var data1 = g.next();
// 执行回调函数传回数据
data1.value(function (data) {
const data2 = g.next(data);
data2.value(function (data2) {
const data3 = g.next(data2);
data3.value(function (data3) {
g.next(data3);
});
});
});
该函数形式单一,基本形式为
d.value(function(data) {
g.next(data);
})
可以利用递归写一个 Thunk自动执行器
function run(fn) {
var g = fn();
function next(data) {
var result = g.next(data);
if (result.done) return;
result.value(next);
}
next();
}
run(gen);
定义了run方法后,直接将Generator
函数作为参数传给run就可以了,函数像多米诺骨牌一样依次执行Generator
函数内的异步操作。当然,前提是每一个yield
命令后面的必须是Thunk
函数。
Thunk
函数是自动执行Generator
函数的一种选择,或者可以像上文那样定义run函数,同样可以使Generator
函数自动执行。
应用
看到这里,如果您接触过Redux-Saga
,你会发现似曾相识也会更好的帮你理解saga
。
参考:
What is the difference between Thunk and currying in JavaScript?
Are thunk and function currying the same?
维基百科
阮一峰
yongningfu