由于js是单线程语言,因此须要异步编程的存在,要不效率过低会卡死。java
以前写过一篇关于Promise的文章,里边写过关于异步的一些概念。这篇文章将会说一下Generator函数的异步应用。node
多个线程互相合做完成任务,在传统的编程语言中(好比java),当A线程在执行,执行一段时间以后暂停,由B线程继续执行,B线程执行结束以后A线程再执行,这个时候,A线程就被称为协程,而这个协程A就是异步任务。npm
function* foo(){ ... //其余代码 var f = readFile(); ... //其余代码 }
上边这个函数,foo函数就是一个协程,经过yield命令实现协程的暂停,等到读取文件的函数执行完毕以后,再继续执行foo其余的操做。编程
Generator函数是协程在ES6的实现,最大的特色是交出函数的执行权(暂停函数执行)json
整个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打印出来,这个看起来很像同步的写法,可是实现起来倒是异步的。async
是否是很简单,若是用回掉函数或者Promise的写法会很复杂的。编程语言
let a = asyncFun() a.next() a.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });
这样的写法表示起来很简洁,可是流程管理比较复杂。异步编程
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函数不是用来替换表达式的,而是用来替换多参数函数的。将其中一个参数替换成只接收一个回掉函数的单参数参数。
听起来很拗口,看代码。
// 正常的写法 fs.readFile(filename,callback); // thunk函数的单参数版本 var thunk = function(filename) { return function(callback) { return fs.readFile(filename,callback); } } var readThunk = thunk(filename) readThunk(callback)
理论上,只要函数的一个参数是回调函数,就能够改写成Thunk函数。
一个转换器,把函数转成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函数的流程管理比较复杂,那么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函数的真正意义在于能够自动执行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 对象也能够作到这一点。
这篇文章写得比较难懂,其实主要是为了下一篇文章作铺垫。