關於generator異步編程的理解以及如何動手寫一個co模塊

generator出現以前,想要實現對異步隊列中任務的流程控制,大概有這麼一下幾種方式:javascript

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

第一種方式想必你們是最多見的,其代碼組織方式以下:java

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

對於一個普通的ajax異步請求來講,我麼在請求開始的時候就要告訴他請求成功以後所要執行的動做,所以就能夠相似以這種方式組織代碼,控制異步流程。這種調用方式最大的問題就是回調黑洞的問題,一層回調也還好,但涉及到二層、三層、n層的時候就讓代碼變得複雜很難維護。node

第二種方式本身在前段時間使用backbone.js做爲技術棧的項目的開發中深有體會,對於每個ajax請求都對其分配一個自定義事件,在ajax成功返回數據的時候,就會觸發自定義的事件完成接下來的動做,控制異步流程,代碼以下:jquery

第三種方式和第二種的方式性質上有些相似,若是從發佈訂閱的角度來看,on方法至關於訂閱者/觀察者,trigger方法至關於發佈者。原理上來講無非就是維護一個「消息中心」的數組,經過on方法訂閱的事件都會推入「消息中心」數組,最後發佈的時候將會匹配「消息中心」數組的事件,進而執行相應的流程。
ajax

咱們經過jquery的sub/pub插件完成一個很簡單的演示。編程

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

 jQuery.subscribe("done", f2);

function f1(){promise

    setTimeout(function () {異步

      // f1的任務代碼函數

      jQuery.publish("done");

    }, 1000);

  }

f1();

 jQuery.publish("done")的意思是,f1執行完成後,向"信號中心"jQuery發佈"done"信號,從而引起f2的執行。

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

咱們只要而且僅須要new一個promise對象,就會發現promise對象的參數函數已經執行了,隔兩秒以後輸出"執行完成"。

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

從本質上來看,Promise是一個構造函數,其自己有all、reject、resolve等方法,同時其原型上有then、catch等方法。經過其用Promise new出來的對象天然就有then、catch方法。而後能夠經過then方法中的回調函數,獲取到上一段異步操做中返回(經過resolve)的數據。從而實現對異步操做的流程控制。

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

綜上所述對於異步流程的控制,都有其自身的缺陷,咱們最理想的方式即是像操做同步流程那樣實現對異步流程的控制,試想一下這樣的異步操做流程(加了層層包裝,proxy即是發送一個異步請求,接下來的代碼即是獲取到異步操做返回的數據,細節可暫時忽略):

這感受就是真他媽的舒服,怎麼實現這麼一個讓人很爽的東西呢,因而咱們的主角---偉大的Generator函數登場了。

先理解這麼本身悟的一句話:

"javascript是單線程的,順序執行一段代碼,執行到了異步操做,按正常的邏輯走的話就是主隊列中的代碼繼續執行,這時異步隊列中的代碼還未執行,咱們繼續執行的代碼也就會發生報錯。那麼解決問題的關鍵就是,咱們可以手動控制代碼的向下執行,配合一個東西監聽到異步操做的已經正常返回了以後,去手動的操做代碼的執行流程,這樣的話就實現了已同步的方式控制異步代碼的執行" 

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

一、咱們是如何實現對於異步操做是否成功返回的監聽。

二、如何手動操做代碼的向下執行。

對於第一個問題,咱們採用的方案是使用promise對象的方式,Promise 的編程思想即是,用於「當xx數據準備完畢,then執行xx動做」這樣的場景,用在這裏再適合不過。

對於第二個問題,咱們即是採用偉大的generator生成器函數,其中的yield特性,可使咱們手動的控制代碼的向下執行。

接下來咱們實際的解決一個問題:實現對於讀取文件異步操做的控制,當讀取完文件以後打印讀取的內容。

咱們依賴於node環境,首先經過promise對其進行封裝,實現數據成功的監聽。咱們手下代碼以下:

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);
        })
    })
} 

有了這個東西,咱們即可以經過其then()表達式,"當數據加載完後,執行某個動做"。那咱們執行的動做是啥,天然就是執行下一步的代碼的操做。繼續看代碼:

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());
}

這個就是一個generator函數的表達式,在這個函數裏面,遇到generator就會執行相似於return的操做。咱們經過next()即可以實現手動的控制代碼的向下執行。

那麼咱們如何控制代碼的執行流程呢,看下面一段:

var g = gen();

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

這段的具體解釋就是,咱們經過promise封裝的對象實現了對於異步操做數據返回的監聽,當數據返回的時候,咱們就經過next()執行下一步的操做,同時把上步操做的值帶入到下一個階段的執行流程之中。

可是上面這段操做非常蛋疼啊,咱們要的是一個能通用的操做流程函數。那麼咱們繼續對這段循環操做進行封裝:

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);

因而一個很是簡單的co模塊便誕生了。
最終代碼以下:

咱們把函數放到run的執行器裏面,便實現了同步操做異步代碼的過程。 

未完待續...

相關文章
相關標籤/搜索