thunk
thunk
从形式上将函数的执行部分和回调部分分开,这样我们就可以在一个地方执行执行函数,在另一个地方执行回调函数。这样做的价值就在于,在做异步操作的时候,我们只需要知道回调函数执行的顺序和嵌套关系,就能按顺序取得执行函数的结果。
以下是 thunk 的简单实现:
function thunkify (fn) { return function () { var args = Array.prototype.slice(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); } } } }
上面的实现将函数原有的执行变为按“执行部分”和“回调部分”分别执行的方式:
fn(a,callback) => thunkify(fn)(a)(callback)
例如:
var fs = require('fs'); var readFile = thunkify(fs.readFile); // 将readFile函数包进thunkify,变为thunkify函数 //**这是执行函数集合**// var f1 = readFile('./a.js'); var f2 = readFile('./b.js'); var f3 = readFile('./c.js'); //**这是回调函数集合**// //利用嵌套控制f1 f2执行的顺序 f1(function(err,data1) { // doSomething f2(function(err,data2) { // doSomething f3(function (err,data3) { // doSomething }) }) })
而传统的写法为:
//传统写法 fs.readFile('./a.js',function(err,data1) { // doSomething fs.readFile('./b.js',data2) { // doSomething fs.readFile('./c.js',data3) { // doSomething }) }) })
在执行部分和回调部分分开之后,就可以使用generator
等异步控制技术方便地进行流程控制,避免回调黑洞。上述的文件读取流程就可以用generator
进行改造:
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); ···· } // 函数的‘回调部分’在另一个地方执行,且调用的形式都一样 var g = gen(); var d1 = g.next(); // 返回的结果为{value: func,done: boolean} // 执行value,实际为执行`d1.value(callback)` // 也即`thunkify(fs.readFile)('./a.js')(callback)` d1.value(function(err,data) { if (err) throw err; // g.next(data) 可以将参数data传回generator函数体,作为上一个阶段异步任务的执行结果 // 例子中,data被传回了gen函数体,作为data1的值 var d2 = g.next(data); d2.value(function(err,data2) { if (err) throw err; g.next(data2); }); });
co
在上述的改造中发现,执行回调部分的时候,依旧存在回调嵌套:d2.value
在d1.value
的回调中执行。观察后发现,其实在执行回调的时候,也就是g
在执行next()
的时候,执行的形式基本相同,都是:
d.value(function(err,data) { if (err) throw err; g.next(data); });
function run(fn) { var g = fn(); // 下一步控制函数,实际就是d.value的回调函数 function next(err,data) { // 把前面一个数据给传递到gen()函数里面 var result = g.next(data); // 判断是否结束 if (result.done) return; // 下一句执行回调next的时候 不断的递归 result.value(next); } // 执行第一步 next(); } // 使用 run(gen);
上面代码中的过程很好理解,就是把gen
放到一个递归器中去执行,在这个递归器中有一个核心的函数next
,这个函数就是递归函数。当函数中的g.next(data)
返回的done
属性值为true
,就表示当前生成器函数中的yield
已经执行完毕,退出就OK。当不为true
,表示当前生成器函数还有未执行的yield
,于是继续调用next
函数继续执行同样的流程。
而上述的流程就是异步流控制库co
的简单实现。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。