generator出現(xiàn)之前,想要實現(xiàn)對異步隊列中任務的流程控制,大概有這么一下幾種方式:

  • 回調函數(shù)

  • 事件監(jiān)聽

  • 發(fā)布/訂閱

  • promise對象

第一種方式想必大家是最常見的,其代碼組織方式如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

function fn(url, callback){ var httpRequest;    //創(chuàng)建XHR
 httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() :  
    window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
  
 httpRequest.onreadystatechange = function(){  if(httpRequest.readystate === 4 && httpRequest.status === 200){  //狀態(tài)判斷   callback.call(httpRequest.responseXML); 
  }
 };
 httpRequest.open("GET", url);
 httpRequest.send();
}
 
fn("text.xml", function(){    //調用函數(shù)
 console.log(this);   //此語句后輸出});
 
console.log("this will run before the above callback.");  //此語句先輸出

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

對于一個普通的ajax異步請求來說,我么在請求開始的時候就要告訴他請求成功之后所要執(zhí)行的動作,因此就可以類似以這種方式組織代碼,控制異步流程。這種調用方式最大的問題就是回調黑洞的問題,一層回調也還好,但涉及到二層、三層、n層的時候就讓代碼變得復雜很難維護。

第二種方式自己在前段時間使用backbone.js作為技術棧的項目的開發(fā)中深有體會,對于每一個ajax請求都對其分配一個自定義事件,在ajax成功返回數(shù)據(jù)的時候,就會觸發(fā)自定義的事件完成接下來的動作,控制異步流程,代碼如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

第三種方式和第二種的方式性質上有些類似,如果從發(fā)布訂閱的角度來看,on方法相當于訂閱者/觀察者,trigger方法相當于發(fā)布者。原理上來說無非就是維護一個“消息中心”的數(shù)組,通過on方法訂閱的事件都會推入“消息中心”數(shù)組,最后發(fā)布的時候將會匹配“消息中心”數(shù)組的事件,進而執(zhí)行相應的流程。

我們通過jquery的sub/pub插件完成一個很簡單的演示。

首先,f2向"信號中心"jQuery訂閱"done"信號。

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

 jQuery.subscribe("done", f2);

function f1(){

    setTimeout(function () {

      // f1的任務代碼

      jQuery.publish("done");

    }, 1000);

  }

f1();

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

 jQuery.publish("done")的意思是,f1執(zhí)行完成后,向"信號中心"jQuery發(fā)布"done"信號,從而引發(fā)f2的執(zhí)行。

第四種方式promise范式,先看一段代碼:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

我們只要并且僅需要new一個promise對象,就會發(fā)現(xiàn)promise對象的參數(shù)函數(shù)已經(jīng)執(zhí)行了,隔兩秒之后輸出"執(zhí)行完成"。

接下來再看一段其實際應用的場景代碼:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

從本質上來看,Promise是一個構造函數(shù),其本身有all、reject、resolve等方法,同時其原型上有then、catch等方法。通過其用Promise new出來的對象自然就有then、catch方法。然后可以通過then方法中的回調函數(shù),獲取到上一段異步操作中返回(通過resolve)的數(shù)據(jù)。從而實現(xiàn)對異步操作的流程控制。

但我的每個函數(shù)都得被promise對象包裝一下,同時一大堆的then...真是一個聽蛋疼的事兒...

綜上所述對于異步流程的控制,都有其自身的缺陷,我們最理想的方式便是像操作同步流程那樣實現(xiàn)對異步流程的控制,試想一下這樣的異步操作流程(加了層層包裝,proxy便是發(fā)送一個異步請求,接下來的代碼便是獲取到異步操作返回的數(shù)據(jù),細節(jié)可暫時忽略):

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

這感覺就是真他媽的舒服,怎么實現(xiàn)這么一個讓人很爽的東西呢,于是我們的主角---偉大的Generator函數(shù)登場了。

先理解這么自己悟的一句話:

"javascript是單線程的,順序執(zhí)行一段代碼,執(zhí)行到了異步操作,按正常的邏輯走的話就是主隊列中的代碼繼續(xù)執(zhí)行,這時異步隊列中的代碼還未執(zhí)行,我們繼續(xù)執(zhí)行的代碼也就會發(fā)生報錯。那么解決問題的關鍵就是,我們能夠手動控制代碼的向下執(zhí)行,配合一個東西監(jiān)聽到異步操作的已經(jīng)正常返回了之后,去手動的操作代碼的執(zhí)行流程,這樣的話就實現(xiàn)了已同步的方式控制異步代碼的執(zhí)行" 

那么問題變成了解決兩個問題。

1、我們是如何實現(xiàn)對于異步操作是否成功返回的監(jiān)聽。

2、如何手動操作代碼的向下執(zhí)行。

對于第一個問題,我們采用的方案是使用promise對象的方式,Promise 的編程思想便是,用于“當xx數(shù)據(jù)準備完畢,then執(zhí)行xx動作”這樣的場景,用在這里再適合不過。

對于第二個問題,我們便是采用偉大的generator生成器函數(shù),其中的yield特性,可以使我們手動的控制代碼的向下執(zhí)行。

接下來我們實際的解決一個問題:實現(xiàn)對于讀取文件異步操作的控制,當讀取完文件之后打印讀取的內容。

我們依賴于node環(huán)境,首先通過promise對其進行封裝,實現(xiàn)數(shù)據(jù)成功的監(jiān)聽。我們手下代碼如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

var fs = require('fs');var readFile = function(fileName) {    return new Promise(function(resolve,reject) {
        fs.readFile(fileName, function(err, data) {            if (err) return reject(err);
            resolve(data);
        })
    })
}

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

有了這個東西,我們便可以通過其then()表達式,"當數(shù)據(jù)加載完后,執(zhí)行某個動作"。那我們執(zhí)行的動作是啥,自然就是執(zhí)行下一步的代碼的操作。繼續(xù)看代碼:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

var gen = function* () {    var f1 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');    var f2 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');

    console.log('<<<<<<<<<<<<<<<<<<<<<<<',f1.toString());
    console.log('>>>>>>>>>>>>>>>>>>>>>>>>',f2.toString());
}

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

這個就是一個generator函數(shù)的表達式,在這個函數(shù)里面,遇到generator就會執(zhí)行類似于return的操作。我們通過next()便可以實現(xiàn)手動的控制代碼的向下執(zhí)行。

那么我們如何控制代碼的執(zhí)行流程呢,看下面一段:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

var g = gen();

g.next().value.then(function(data){
  g.next(data).value.then(function(data){
    g.next(data);
  });
});

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

這段的具體解釋就是,我們通過promise封裝的對象實現(xiàn)了對于異步操作數(shù)據(jù)返回的監(jiān)聽,當數(shù)據(jù)返回的時候,我們就通過next()執(zhí)行下一步的操作,同時把上步操作的值帶入到下一個階段的執(zhí)行流程之中。

但是上面這段操作很是蛋疼啊,我們要的是一個能通用的操作流程函數(shù)。那么我們繼續(xù)對這段循環(huán)操作進行封裝:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

function run(gen){  var g = gen();  function next(data){    var result = g.next(data);    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

run(gen);

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

于是一個非常簡單的co模塊便誕生了。
最終代碼如下:

大學生就業(yè)培訓,高中生培訓,在職人員轉行培訓,企業(yè)團訓

我們把函數(shù)放到run的執(zhí)行器里面,便實現(xiàn)了同步操作異步代碼的過程。 

未完待續(xù)...