JavaScript異步co

上三篇分別介紹了PromiseGenerator,包括用Thunk自動(dòng)執(zhí)行Generator,本篇介紹用co模塊將Generator和Promise結(jié)合起來,自動(dòng)執(zhí)行Generator。co模塊參照這里,以前co模塊返回的是Thunk,現(xiàn)在返回的是promise。安裝命令:npm install co

你可以從Github上獲取本篇例子代碼。仍舊以讀取文件為例,先用Promise來執(zhí)行異步操作:

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

readFile('./apples.txt', 'utf8').then(function(data){
    console.log(data);
    return readFile('./oranges.txt', 'utf8'); 
}).then(function(data){
    console.log(data);
}).catch(function(error){
    console.log(error);
});

先將異步函數(shù)fs.readFile封裝成一個(gè)Promise對象,起名readFile。然后調(diào)用then依次執(zhí)行異步操作。

但看起來一堆then,代碼不夠清晰,改成用Generator來定義異步操作的步驟:

var gen = function* () {
    var f1 = yield readFile('./apples.txt', 'utf8');
    console.log(f1);
    var f2 = yield readFile('./oranges.txt', 'utf8');
    console.log(f2);
};

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

有了上兩篇的基礎(chǔ),這些代碼應(yīng)該很容易就能看懂了。用Generator定義異步步驟,確實(shí)比Promise的then清晰很多,維護(hù)方便。但Generator的問題是,一步步執(zhí)行next很麻煩。我們需要讓Generator自動(dòng)執(zhí)行起來:

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方法的擴(kuò)展, 因?yàn)樵创a非常短,干脆直接貼出來看看它是怎么實(shí)現(xiàn)的:(為縮減篇幅,刪除了源代碼里的注釋)

function co(gen) {  //co函數(shù)接受Generator函數(shù)作參數(shù),最終返回一個(gè) Promise 對象
  var ctx = this;
  var args = slice.call(arguments, 1);

  return new Promise(function(resolve, reject) {        //co函數(shù)返回的是一個(gè)Promise對象
    //先檢查參數(shù),如果是Generator函數(shù)就獲得一個(gè)遍歷器對象。
    //如果不是,或沒有next方法,就將其轉(zhuǎn)成一個(gè)狀態(tài)為Resolved的Promise對象,并返回。
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();  //Generator函數(shù)的next方法被封裝在此處

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);    //關(guān)鍵在這里,詳見下面next方法
      return null;
    }

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    //該函數(shù)會無限遞歸,直到Generator函數(shù)執(zhí)行完畢為止
    function next(ret) {
      //返回值的done屬性為true表示Generator函數(shù)遍歷完畢,返回Promise對象
      if (ret.done) return resolve(ret.value);

      //源碼中定義了toPromise方法可以將Object,Array,Thunk,Generator轉(zhuǎn)成Promise對象。
      //即你需要確保Generator函數(shù)的yield命令后面,是這些對象
      var value = toPromise.call(ctx, ret.value);

      //步步都確保已經(jīng)是Promise對象后,執(zhí)行then方法,通過onFulfilled再次調(diào)用該函數(shù)
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

      //參數(shù)有誤的話,捕捉異常,中止處理
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

上面co源代碼加上注釋后應(yīng)該可以理解。用co模塊自動(dòng)執(zhí)行Generator函數(shù)只需一行代碼:co(gen);。是不是簡單到令人發(fā)指?完整代碼如下:

var fs = require('fs');
var co = require('co');

var readFile = function (fileName, options) {
    return new Promise(function (resolve, reject){
        fs.readFile(fileName, options, function(error, data){
            if (error) return reject(error);
            resolve(data);
        });
    });
};
var gen = function* () {
    var f1 = yield readFile('./apples.txt', 'utf8');
    console.log(f1);
    var f2 = yield readFile('./oranges.txt', 'utf8');
    console.log(f2);
};
co(gen);

先定義一個(gè)Promise對象將異步函數(shù)封裝起來,用Generator函數(shù)定義異步操作流程,co模塊里將yield命令后的異步操作包裝成一個(gè)Promise對象,用then交回執(zhí)行權(quán)來遞歸自動(dòng)執(zhí)行Generator函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,404評論 5 22
  • 本文首發(fā)在個(gè)人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云閱讀 1,755評論 0 3
  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,882評論 0 5
  • 異步編程在JavaScript中非常重要。過多的異步編程也帶了回調(diào)嵌套的問題,本文會提供一些解決“回調(diào)地獄”的方法...
    AlienZHOU閱讀 28,221評論 2 52
  • 異步編程 程序執(zhí)行分為同步和異步,如果程序每執(zhí)行一步都需要等待上一步完成才能開始,此所謂同步。如果程序在執(zhí)行一段代...
    faremax閱讀 208評論 0 0

友情鏈接更多精彩內(nèi)容