ES6&ES7中的異步之Generator函數與異步編程

Generator函數與異步編程

由於js是單線程語言,因此須要異步編程的存在,要不效率過低會卡死。java

傳統的異步方法

  • 回調函數
  • 事件監聽
  • 發佈/訂閱
  • Promise

以前寫過一篇關於Promise的文章,裏邊寫過關於異步的一些概念。這篇文章將會說一下Generator函數的異步應用。node

Generator函數

協程

多個線程互相合做完成任務,在傳統的編程語言中(好比java),當A線程在執行,執行一段時間以後暫停,由B線程繼續執行,B線程執行結束以後A線程再執行,這個時候,A線程就被稱爲協程,而這個協程A就是異步任務。npm

function* foo(){
    ... //其餘代碼
    var f = readFile();
    ... //其餘代碼
}

上邊這個函數,foo函數就是一個協程,經過yield命令實現協程的暫停,等到讀取文件的函數執行完畢以後,再繼續執行foo其餘的操做。編程

協程的Generator函數實現

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函數

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 對象也能夠作到這一點。

這篇文章寫得比較難懂,其實主要是爲了下一篇文章作鋪墊。

相關文章
相關標籤/搜索