由於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 對象也能夠作到這一點。
這篇文章寫得比較難懂,其實主要是爲了下一篇文章作鋪墊。