DaraW

Code is Poetry

node-thunkify源码阅读笔记

Node7发布后已经可以通过添加--harmony-async-await的参数调用来直接支持async/await语法了,据说Node8还会进一步推进其发展,于是研究了一下JS的异步流程控制和下一代Node Web框架Koa2

关于generator async/await的发展史已有一大堆文章讲过了,这里不再赘述。
tj的coKoa2上个大版本Koa1的核心,在没有async/await的时候一般会借助co来做自动流程控制。关于co的源码分析文章也有很多,代码不长值得一读,参考了一些分析文章也算是了解了其逻辑和思路。

co中出现了一个thunkToPromise的函数,一些文章都跳过了这个并表示thunk函数已经没什么意义了,但本着好奇心读了阮一峰的Thunk 函数的含义和用法,文中一个地方一时没有搞懂,故写此文记录一下。

thunkify代码很少,就是一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function thunkify(fn){
assert('function' == typeof fn, 'function required');

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);
}
}
}
};

Demo:

1
2
3
4
5
6
7
8
9
function f(a, b, callback){
var sum = a + b;
callback(sum);
callback(sum);
}

var ft = thunkify(f);
ft(1, 2)(console.log);
// 3

让我一时没有搞懂的是为什么借助called标记能够确保回调函数只执行一次,借助VS Code的断点调试把文中示例的代码跑了一遍总算搞懂了:

Demo中首先真正执行的是thunkify(f)f函数传入thunkify后直接返回了一个闭包,这里称之为闭包1,闭包1被赋值给了ft,ft即为闭包1的一个引用。
接着执行的是ft(1, 2)ft中传入了(1, 2)来执行,ft中将duck type的伪数组arguments保存为一个真实数组args,所以此时args数组中有两个成员即12ft最后又返回了一个闭包,这里称之为闭包2。
接着执行的是ft(1, 2)(console.log),也就是将console.log传入闭包2来执行,传入的console.log即形参done其实就是一开始f的回调函数,这时候重点来了,在闭包2中增加了一个标记called来记录回调是否执行过一次了,而pushargs数组的函数则已经不是单纯的回调,而是被包裹了原回调、保证只会执行一次的函数

1
2
3
4
5
function(){
if (called) return;
called = true;
done.apply(null, arguments);
}

最后执行的就是fn.apply(ctx, args),而thunkify函数的形参fn对应的就是一开始传入的f函数,所以总的看来,真正执行的还是最初的f函数,而被改变的是传入的回调,回调被包裹了一层,借助called标记来保证只会被执行一次,执行过后called标记被改变,不再会被执行。

Proudly powered by Hexo and Theme by Hacker
© 2022 DaraW