对阮一峰老师的thunk函数一节个人的补充理解
thunk函数的作用非常简单 其实就是把一个函数的 执行参数 和 回调分成两个函数(可以把函数本身也进行包装) 只要是有回调函数的 就可以用thunk进行包装
比如thunk把
fn(args, callback)--->变成--->thunk(fn)(args)(callback)
thunkify的代码
function thunkify(fn) {
return function() {
var args = Array.prototype.slice.call(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);
}
}
}
}
这有什么用呢?
因为thunk 形式上把回调函数和执行函数分开,所以我们可以做到在 一个地方执行执行函数,其他地方执行回函数, 而且对于很对异步操作来说, 想想, 那我们是不分可以把这些执行函数全部放在一块执行, 他们对应的回调函数放在其他地方执行,而且只是需要确定回调函数执行的顺序和嵌套关系, 就能取得执行函数的返回顺序
用代码解释一下上面的信息
var fs = require('fs');
var readFile = thunkify(fs.readFile);
//这是执行函数集合
var f1 = readFile('./a.js');
//用户自定义的逻辑在这?
var f2 = readFile('./b.js');
var f3 = readFile('./c.js');
//这是回调函数集合
//利用嵌套控制f1 f2执行的顺序
f1(function(err, data1) {
//还是用户定义的逻辑在这?
f2(function(err, data2) {
f3(function (err, data3) {
})
})
})
//传统写法
fs.readFile('./a.js', function(err, data1) {
//传统用户定义的逻辑在这
fs.readFile('./b.js', function(err, data2) {
fs.readFile('./c.js', function(err, data3) {
})
})
})
可以看到把执行函数和回调函数分开以后,代码清晰了许多 但是问题来了 用户自定义的逻辑应该放在哪
首先先说一点 回调函数的一个作用就是获取数据的
那么对应thunk定义的函数来说,用户自定义的逻辑到底是放在 回调函数的集合这边还是放在 执行函数集合那边
如果用户自定义的逻辑是放在回调函数集合那边, 有两个缺点
- 代码逻辑里面上面来说不符合常规逻辑
- 回调函数里面嵌套逻辑处理太多的话,那thunk的优势就没了
那就确定了把用户自定义的逻辑放在执行函数的基本一端,回调函数只是负责获取数据,并在数据传回执行函数集合
所以现在的基本流程就是
执行函数执行-->等待回调函数传回数据-->用户对于获取的数据进行操作
等待传回数据是不是想到了 gennerator yield
所以就有了thunkify和generator的完美结合
如何结合?
把所有的执行函数放入generator函数里面,利用generator函数的yield对执行函数的流程控制 把函数执行权移出函数到对应的回调函数,获取数据后再把数据返回来
利用fs.readFile举例子
利用thunk把fs.readFile(arguments, callback) 执行的参数和回调函数分开 从而变成 执行函数放在一起 回调函数放在一起 利用yield进行连接
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);
}
//写个执行函数
//发现callback在一起 而且调用的形式都一样
var g = gen();
var d1 = g.next();
//执行value 实际为执行总函数 -->回调函数
d1.value(function(err, data) {
if (err) throw err;
//传回数据
var d2 = g.next(data);
d2.value(function(err, data2) {
if (err) throw err;
g.next(data2);
});
});
发现上面的g的执行形式单一
基本形式为
d.value(function(err, data) {
if (err) throw err;
g.next(data);
})
可以利用递归写一个run函数 每个下一个都只和回调函数 callback(err ,data)有关 提取callback(err, data)
function run(fn) {
var g = fn();
//下一步----实际就是回调函数
function next(err, data) {
//把前面一个数据给传递到gen()函数里面
var result = g.next(data);
//判断是否结束
if (result.done) return;
//下一句执行回调next的时候 不断的递归
result.value(next);
}
//执行第一步
next();
}
run(gen);