最近一直都在看些源码,源码学习不仅能提高自身的代码阅读能力,还能学习优秀框架的设置思路,所以多多益善啊!今天要介绍的是 thunkify 和 thunkify-wrap 框架,前者是大神TJ开发的,后者是国内Node社区的活跃贡献者dead-horse开发的。本文主要是作为笔者学习的一个记录,如果有表达不准确或者错误的地方,还望指出。
thunkify介绍
在介绍前我们先来看个栗子:
|
|
read是一个异步读取文件的函数,cb为读取完后的回调函数,很普通也很常见。那么,我们来把read改造下:
|
|
改造后的read返回一个函数,且只有一个参数,回调函数成了返回函数的参数。
thunkify的功能就是把一个普通的函数改造成一个thunk[θʌŋk]
函数。在这里,read就是一个thunk函数。那么,thunkify到底有什么用处呢?
thunkify实战
thunkify的真正价值,是结合 co 框架才能体现出来(其实co本身也是一个thunk函数),现在正流行的 koa 框架就是基于co框架开发的。我们看一段结合co框架的代码:
|
|
在yield
后面返回了一个改造后的readFile
函数,在这里还未真正执行这个异步函数,那么到底是在哪里调用的呢?先别急,我们来看看co内部到底是怎么实现的,下面是一个简易的co框架(实现原理详解):
|
|
请注意第14行代码ret.value(next)
,这是co框架的核心所在。上文中readFile函数也在这里得到了调用。因为这里ret.value
的值就是readFile这个thunk函数,co中的next
函数也通过参数传递得以继续执行。再次调用next
方法时,args
已经是package.josn
的内容了。我们继续往下执行,通过gen.next(args)
,把读取到的内容赋值给外面的data
变量。此时ret.done
的值为true
,因为没有yield
了,那么执行回调函数cb
。我们前面已经说过,co本身也是一个thunk
函数,所以这里的cb
就是function(err, content) {console.log(content);}
,package.josn
的内容最终也在这里得到了输出。是不是觉得很奇妙,这就是thunk函数的魅力所在。
在阅读thunkify源码的时候,其中有个called
变量产生了些疑惑,不过后来也在这里找到了解释。为了防止多次调用ret.value
而造成错误,做了不得已的取舍。
thunkify-wrap介绍
thunkify-wrap框架是对thunkify的一个扩展。在原有基础上:
- 增加了多个函数封装成thunk函数
- 把函数封装成一个GeneratorFunction函数
- 增加事件支持
- 允许传递上下文
(ctx)
对多个函数的封装:
|
|
传递user对象,内部通过使用for循环,使对象中的每一个函数经过thunkify
函数包装成为一个thunk
函数。后两句指对其中add
和show
进行处理和只对add
进行处理。
在介绍如何封装成一个GeneratorFunction函数之前,我们先来了解下generator中一个重要的概念,代理(delegating yield
)。
|
|
在执行到第三个next
的时候,直接输出了start
并且在yield 3
处暂停了。等到gen2
中的return
返回,再继续执行gen1
中余下的yield
。
我们通过一个栗子来看下thunkify-wrap是怎么结合co框架来执行一个GeneratorFunction的:
|
|
注意第5行代码,fs.readFile
经过genify
的处理后,此时已经是一个GeneratorFunction,因为在thunkify-wrap内部,判断如果传入的fn
已经是GeneratorFunction了,直接返回,如果不是,则返回一个GeneratorFunction。详见代码:
|
|
所以上面的代码可以改为类似这样:
|
|
是不是觉得和上面的delegating yield
的栗子差不多了。在co中的var ret = gen.next(args)
第一次调用时,程序通过代理在readFile
中的yield
处暂停,此时ret.value
的值为fs.readFile
函数。注意此时的fs.readFile
已经通过thunkify
处理成一个thunk函数了,执行ret.value(next)
语句进行fs.readFile
的调用,co中的next
方法通过回调函数的形式再次被调用,而此时的args
已经是package.json
的内容了。第二次执行gen.next(args)
时把文件内容赋值给外部readFile
中的变量data
,通过return
返回到第一个GeneratorFunction中。因为没有yield
了,所以co中ret.done
的值为true
,直接调用回调函数输出文件内容。
小结
thunkify和thunkify-wrap框架是co框架的基石,也是koa框架的重要组成部分,所以先了解他们的实现对后面学习koa还是很有帮助的。起初学习的时候,代码看的会有些晕,但了解了他在co中的实现后,就能更好的理解他为什么要设计成这样。恩,that’s all.