異步調用,真的沒有那麼可怕!

異步編程對於開發新手來講,簡直是噩夢般存在,可是又很是重要,非學不可。其實啊,異步調用,並無那麼可怕。javascript

傳統方法

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

基本概念

異步

所謂的異步,簡單來講,就是任務不是一次連續完成的,中間加入了其餘的程序運算,等第一階段準備好了數據,再返回來進行計算。前端

回調函數

回調函數的名字,callback,這個相信作過前端開發的小夥伴都見到過。javaScript語言對異步編程的實現就是回調函數。
舉個例子:java

fs.readFile('etc/passwd','utf-8',function(err,data){
    if(err) throw err;
    console.log(data);
});
複製代碼

Promise

回調函數自己沒有問題,問題在於,出現多個回調函數嵌套上,也就是咱們俗稱的回調地獄。node

let url1 = 'http://xxx.xxx.1';
    let url2 = 'http://xxx.xxx.2';
    let url3 = 'http://xxx.xxx.3';
    $.ajax({
        url:url1,
        error:function (error) {},
        success:function (data1) {
            console.log(data1);
            $.ajax({
                url:url2,
                data:data1,
                error:function (error) {},
                success:function (data2) {
                    console.log(data2);
                    $.ajax({
                        url:url3,
                        data,
                        error:function (error) {},
                        success:function (data3) {
                            console.log(data3);
                        }
                    });
                }
            });
        }
    });
複製代碼

這種代碼自己邏輯能夠實現,問題處在下面幾個方面:git

  • 代碼臃腫。
  • 可讀性差。
  • 耦合度太高,可維護性差。
  • 代碼複用性差。
  • 容易滋生 bug。
  • 只能在回調裏處理異常。

在之前的章節中,咱們講到過Promis函數,他就是典型的解決函數異步問題而產生的。 他是回調函數的新寫法。程序員

舉個例子:github

function request(url,data = {}){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url,
            data,
            success:function (data) {
                resolve(data);
            },
            error:function (error) {
                reject(error);
            }
        })
    });
}

let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
request(url1)
    .then((data)=>{
        console.log(data);
        return request(url2,data)
    })
    .then((data)=>{
        console.log(data);
        return request(url3,data)
    })
    .then((data)=>{
        console.log(data)
    })
    .catch((error)=>{
        console.log(error);
    });

複製代碼

相對上面來講,使用Promis對象後,代碼結構變得清晰的許多。但,問題是,一眼看上去,多了好多個then,並且,不寫註釋,很難明白究竟是什麼意思。ajax

Generator函數

定義:

generator函數是ES6提供的一種異步編程解決方案,語法與傳統函數徹底不一樣。 執行Generator函數會返回一個遍歷器對象。返回的遍歷器對象能夠依次遍歷Generator函數內部的每個狀態shell

上面的章節中,咱們提到過,Genertor函數,最大的特色,就是能夠交出函數的執行權。也就是說,能夠暫停執行函數。編程

function * gen(x) {
  var y  = yield  x + 1;
  return y;
}
var g = gen(1);
g.next(); // {value:2,done:false}
g.next(); // {value:undefined.done:true}
複製代碼

執行gen函數,返回的是函數指針,調用指針函數的next方法能夠移動內部函數的指針,指向第一個遇到的yield語句。

異步封裝

下面看一個使用Generator函數執行一個真實的異步任務。

var fetch = require('node-fetch);
  
  function * gen() {
    var url = 'http://api.github.com';
    var result = yield fetch(url);
    console.log(result.bio);
  }
  
  var g = gen();
  var result = g.next();
  
  result.value.then(function(data) {
    return data.JSON();
  }).then(function(data) {
    g.next(data);
  })
  
複製代碼

上面的代碼將異步表達的很清晰,可是流程管理並不方便,還的本身調用next方法。

Generator函數自動流程管理

例子:

function * gen() {
  //...
}

var g = gen();
var res = g.next();
while(!res.done){
    console.log(res.value);
    g.next()
}
複製代碼

上面代碼能自動完成,前提是不適合異步,若是必須保證前一步執行完才能執行後一步,上面的自動執行就不行了。

Thunk函數

如何自動完成異步函數呢?引出咱們的主角,thunk函數。

var fs = requier('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function *() {
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
} 
複製代碼

上面代碼,執行異步操做後(yield 後面的表達式),須要將執行權再返回給Generator函數,咱們先手動執行一下。

var g = gen();

var r1 = g.next();
r1.value(function(err,data) {
  if(err) throw err;
  var r2 = g.next(data);
  r2.value(function(err,data) {
    if(err) throw err;
    g.next(data);
  })
})

複製代碼

仔細看一下,上面的代碼,generator函數執行過程其實就是將同一個回調函數反覆傳入next方法的value屬性。
如何自動完成這個操做呢?

function run (fn) {
  var gen = fn();
  function next(err,data) {
    var result = gen.next(data);
    if(result.done) return ;
    result.value(next)
  }
  next();
}

function* g() {
  //...
}

run(g);
複製代碼

上面代碼中的run函數就是一個Generator函數的自動執行器。內部的next函數就是Thunk的回調函數,next先將指針移動到Generator函數的下一步,而後判斷Generator函數是否結束, 若是沒結束,就將next函數再傳入thunk函數,不然直接退出。

這就方便多了。只需將要執行的函數,放入run()函數就能夠了。媽媽不再擔憂個人異步調用了。

CO

每次都寫run函數麼?很煩餒~
那就嘗試一下CO。
co模塊是著名的程序員TJ Holowaychuk於2013年6月發佈的一個小工具,用於Generator函數自動執行。
怎麼用呢?至關簡單了。

var co = require('co');
co(gen);
複製代碼

至關簡便。並且,co函數返回的是一個Promise對象,能夠調用then方法添加回調函數。

co(gen).then(function() {
  console.log('到這函數就都執行完了。')
});
複製代碼

爲何co能自動執行呢?

generator函數是一個異步執行容器。它的自動執行須要一中機制,當異步操做有告終果,須要自動交回執行權。
解釋一下:

function * gen(x) {
  var y = yield x = 1;
  var z = yield 1+1;
  return y, z;
}
複製代碼

當執行到第一個yield的時候,調用next方法,獲取到值。再次調用next,繼續執行,再次調用這步驟,不用人爲調用了。
co模塊其實就是將thunk函數和promise對象包裝成一個模塊。使用的前提是,yield後面的表達式必須是thunk函數或者promise對象。4.0以後的co版本只支持promise對象了。

有時間的同窗能夠看看co的源碼,也很簡單的,這裏就不作過多的介紹了。

相關文章
相關標籤/搜索