惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
Y
Y Combinator Blog
博客园 - Franky
D
Docker
B
Blog RSS Feed
M
MIT News - Artificial intelligence
雷峰网
雷峰网
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
S
SegmentFault 最新的问题
GbyAI
GbyAI
Recent Announcements
Recent Announcements
Martin Fowler
Martin Fowler
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
B
Blog
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
WordPress大学
WordPress大学
Vercel News
Vercel News
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News

DaraW

GMTC 北京 2021 小程序开发实践专场所见所想 从一次 Bug 定位来看 Taro H5 的组件实现 浅谈 B 站新 BV 号的设计 复盘:Vue.js 是如何成功的 你可能不知道的 Git React 应用性能优化一例 Thrift RPC Mock 方案探索 前端视频质量监控 前端函数式编程 多说一句再见 《你不知道的JS上卷》阅读小记之setTimeout的this指向问题 ES6实现内部类的写法 IntelliJ IDEA在Linux下字体不正常解决方案 如何监听JS变量的变化 JS实现自定义事件 制作列带有斑马条纹背景的表格 git用户名莫名其妙变化及挽救措施 记一次前端性能优化实战 将setTimeout函数队列
node-thunkify源码阅读笔记
DaraW · 2016-11-12 · via DaraW

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

让我一时没有搞懂的是为什么借助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标记被改变,不再会被执行。