Generator函数与异步编程
因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。
传统的异步方法
- 回调函数
- 事件监听
- 发布/订阅
- Promise
之前写过一篇关于Promise的文章,里边写过关于异步的一些概念。这篇文章将会说一下Generator函数的异步应用。
Generator函数
协程
多个线程互相合作完成任务,在传统的编程语言中(比如java),当A线程在执行,执行一段时间之后暂停,由B线程继续执行,B线程执行结束之后A线程再执行,这个时候,A线程就被称为协程,而这个协程A就是异步任务。
function* foo(){
... //其他代码
var f = readFile();
... //其他代码
}
上边这个函数,foo函数就是一个协程,通过yield命令实现协程的暂停,等到读取文件的函数执行完毕之后,再继续执行foo其他的操作。
协程的Generator函数实现
Generator函数是协程在ES6的实现,最大的特点是交出函数的执行权(暂停函数执行)
整个Generator函数就是一个封装好了的异步任务,而yield是函数暂停执行的标志。
function* foo(){
let x = 1;
let y = yield x + 2;
return y
}
var f = foo()
f.next(); // {value:3,done:false}
f.next(); // {value:undefined,done:true}
next方法的作用是分批端执行Generator函数。
异步函数的封装
let fetch = require('node-fetch')
function* asynsFun(){
let url = '....';
var f = yield fetch(url);
console.log(f)
}
当执行完fetch之后把取回的数据赋值给f,然后再把f打印出来,这个看起来很像同步的写法,但是实现起来却是异步的。
是不是很简单,如果用回掉函数或者Promise的写法会很复杂的。
let a = asyncFun()
a.next()
a.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
这样的写法表示起来很简洁,但是流程管理比较复杂。
Thunk函数
thunk函数是自动执行Generator函数的一种方法。
Thunk函数的核心理解就是传名调用。
function f(x){
return x * 2
}
f(x + 6)
//等同于
var thunk = function(x) {
return x + 5
}
function f() {
return thunk() * 2
}
理论上,x+6被thunk函数替代了,所有用到原来参数的地方,直接用thunk求值就行。
这就是Thunk函数的策略,是传名求值的一种实现策略,用来替换某个表达式。
js中的Thunk函数
在js中,函数的参数并不是传名的而是传值的,所以,thunk函数不是用来替换表达式的,而是用来替换多参数函数的。将其中一个参数替换成只接收一个回掉函数的单参数参数。
听起来很拗口,看代码。
// 正常的写法
fs.readFile(filename,callback);
// thunk函数的单参数版本
var thunk = function(filename) {
return function(callback) {
return fs.readFile(filename,callback);
}
}
var readThunk = thunk(filename)
readThunk(callback)
理论上,只要函数的一个参数是回调函数,就可以改写成Thunk函数。
Thunkify模块
一个转换器,把函数转成Thunk函数
安装
npm install thunkify
使用方法:
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(rs.readFile);
read('package-json')(function(err,str)
// ...
)
thunkify接受一个回调方法、
Generator函数的流程管理
之前说过,Generator函数的流程管理比较复杂,那么Thunk函数有什么用呢,正确答案是,他可以帮助Generator函数实现自动的流程管理。
function* gen(){
// ...
}
var g = gen();
var res = g.next();
while(!res.done){
console.log(res.value)
res.next();
}
理论上,上面的代码可以实现自动执行,但是,不能适合异步。用Thunk可以解决这个问题。
var thunkify = require('thunkify');
var fs = require('fs');
var readFileThunk = thunkify(fs.readFile)
var gen = function* (){
var r1 = readFileThunk('filename1')
console.log(r1);
var r2 = readFileThunk('filename2')
console.log(r2);
}
Thunk函数的自动流程管理
Thunk函数的真正意义在于可以自动执行Generator函数,看下边的例子。
function* g(){
// ...
}
function run(fn){ //Thunk函数接收一个Generator函数
var gen = fn();
function next(err,data){
var result = gen.next(data);
if(result.done) return;
return result.value(next)
}
next();
}
run(g)
解析一下这个代码:
run方法其实就是一个Generator函数自动执行器。内部函数next就是Thunk的回调函数,next函数首先把Generator函数的指针指向Generator函数的下一步方法(gen.next()),如果没有,就把next函数传给Thunk函数(result.value属性),否则直接退出。
有了这个执行器,执行Generator函数就方便多了,不管内部多少操作,直接把Generator函数传给run函数即可,当然前提是每一个异步操作都是一个Thunk函数,也就是yield后面的必须是Thunk函数。
function* g(){
var f1 = yield fs.readFileThunk('filename1')
var f2 = yield fs.readFileThunk('filename2')
...
}
run(g)
Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。
这篇文章写得比较难懂,其实主要是为了下一篇文章做铺垫。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。