成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

thunkify與co源碼解讀

Tangpj / 2746人閱讀

開頭 首先本文有將近3000字,閱讀可能會(huì)占用你20分鐘左右。 文筆可能不佳,希望能幫助到閱讀此文的人有一些收獲

在進(jìn)行源碼閱讀前
首先抱有一個(gè)疑問,thunk函數(shù)是什么,thunkify庫又是干什么的,co又是干嘛,它有啥用

程序語言有兩種求值策略 傳名調(diào)用 傳入?yún)?shù)實(shí)際上是傳入函數(shù)體 傳值調(diào)用 函數(shù)體在進(jìn)入的時(shí)候就進(jìn)行運(yùn)算計(jì)算值
編譯器的"傳名調(diào)用"實(shí)現(xiàn),往往是將參數(shù)放到一個(gè)臨時(shí)函數(shù)之中,再將這個(gè)臨時(shí)函數(shù)傳入函數(shù)體。這個(gè)臨時(shí)函數(shù)就叫做 Thunk 函數(shù)。

在 JavaScript 語言中,Thunk 函數(shù)替換的不是表達(dá)式,而是多參數(shù)函數(shù),將其替換成單參數(shù)的版本,且只接受回調(diào)函數(shù)作為參數(shù)

這幾句話來自阮一峰老師的blog文章

試想下我們在node環(huán)境下要使用fs.readfile

fs.readfile("filename",function(err,data){
     if(err){
          console.log(err)
          return
     }
})

而使用thunk簡單改造之后我們的函數(shù)可以變成這樣子的形式

var Thunk = function(filename){
    return function (callback){
        return fs.readfile(fileName,callback)
    }
}

此時(shí)調(diào)用readfile的話,我們可以這么調(diào)用

var read = Thunk("filename")
read(callback);

thunkify出自tj大神之手

thunkify源碼解析
var assert = require("assert");
module.exports = thunkify;
function thunkify(fn) {
    assert("function" == typeof fn, "function required");
    // 引入斷言庫判斷是不是函數(shù)
    // 返回一個(gè)包含thunk函數(shù)的匿名函數(shù)
    return function () {
        var args = new Array(arguments.length);
        // 創(chuàng)建一個(gè)數(shù)組空間
        var ctx = this;
        // 獲取上下文環(huán)境用于后面綁定上下文

        for (var i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }
        // 迭代傳參,因?yàn)橛袃?nèi)存泄漏bug
        // 返回真正的thunk函數(shù)
        return function (done) {
            // done相當(dāng)于是執(zhí)行后的callback
            var called;
            // 聲明一個(gè)called保證只執(zhí)行一次這個(gè)回調(diào)函數(shù)
            // 壓入一個(gè)數(shù)組中進(jìn)行這種隔斷,防止被多次執(zhí)行
            args.push(function () {
                if (called) return;
                called = true;
                done.apply(null, arguments);
            });
            // 用try catch 在執(zhí)行失敗也走一次callback 傳入err信息
            try {
                fn.apply(ctx, args);
            } catch (err) {
                done(err);
            }
        }
    }
};

代碼并不難懂
乍一看,這好像沒什么用吧。

但 js后來有一個(gè)Generator函數(shù),thunk此時(shí)仿佛有了作用

Generator函數(shù)

使用yield 就是將控制權(quán)放出暫停執(zhí)行
然后返回一個(gè)當(dāng)前指針(遍歷器對象)

所以我們是否需要有一種方法接受并且可以繼續(xù)返回這種控制權(quán)
顯式的調(diào)用next固然沒有問題。但是我們要自動(dòng)的話?該怎么辦

基于自動(dòng)流程管理,我們利用thunk函數(shù)的特性,調(diào)用回調(diào)函數(shù)callback
回調(diào)函數(shù)里面遞歸調(diào)用generator的next方法
直到狀態(tài)值為done generator函數(shù)結(jié)束
這時(shí)候整個(gè)generator就可以很優(yōu)雅地被解決

然后我們想象,這個(gè)流程thunk函數(shù)可以干什么

主要的功能其實(shí)是通過封裝多層使得我們可以在回調(diào)函數(shù)內(nèi)獲得控制權(quán)
返回控制權(quán)
因?yàn)橐话惆凑照懛?br>我們需要顯式地調(diào)用next next來使得我們的Generator一步步完成
那么我們只需要一種機(jī)制,可以幫助我們獲得控制權(quán),并且返回控制權(quán)
都可以實(shí)現(xiàn)自動(dòng)化

var fs = require("fs");
var thunkify = require("thunkify");
var readFile = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFile("xxxfilename");
  console.log(r1.toString());
  var r2 = yield readFile(" xxxfilename ");
  console.log(r2.toString());
};

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

這是一個(gè)簡單的demo利用thunkify實(shí)現(xiàn)自動(dòng)化generator

thunk函數(shù)回調(diào)調(diào)用next是一種方法
Pormise的then調(diào)用next 同時(shí)也是一種解決辦法
區(qū)別在于thunk可控(指的是在回調(diào)中我們可以可控執(zhí)行),promise立即執(zhí)行

co是什么

Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

基于Generator,使用promise,讓你用一種更好的方式書寫異步代碼

co的源碼也并不多
大概兩百行
https://github.com/tj/co/blob...
要讀懂co源碼建議還得看看promise規(guī)范與用法

co源碼解析
var slice = Array.prototype.slice;
module.exports = co["default"] = co.co = co;
co.wrap = function (fn) {
  //兼容有參數(shù)的generator函數(shù)
  //利用柯里化將generator轉(zhuǎn)換成普通函數(shù)
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};
function co(gen) {
  var ctx = this;
  //獲得當(dāng)前上下文環(huán)境
  var args = slice.call(arguments, 1);
  //獲得多參數(shù)(如果有的話)
  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  //會(huì)內(nèi)存泄漏
  //返回一個(gè)promise相當(dāng)于將一切都包裹在promise里面。使得我們co返回的可以使用promise的方法
  // co的返回值是Promise對象。為什么可以then和catch的根源
  return new Promise(function(resolve, reject) {
    //做類型的判斷。
    if (typeof gen === "function") gen = gen.apply(ctx, args);
    //Generator函數(shù)執(zhí)行之后會(huì)是typeof會(huì)是對象。
    //默認(rèn)執(zhí)行調(diào)用一次Generator返回一個(gè)遍歷器對象Generator
    if (!gen || typeof gen.next !== "function") return resolve(gen);
    //判斷類型 如果不符合  promise就進(jìn)入resolved
    // 看看是不是Generator指針
    //傳入的不是Generators函數(shù),沒有next,
    // 就直接resolve返回結(jié)果;這里是錯(cuò)誤兼容而已,因?yàn)閏o就是基于generator function的,傳入其他的沒有意義

    //執(zhí)行onFulfilled
    onFulfilled();
    //返回一個(gè)promise

    //onFulfilled干了什么。其實(shí)跟我們之前的一樣,只是這里涉及到了promise的狀態(tài)。如果出錯(cuò)了。狀態(tài)返回是reject
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
        //初始化啟動(dòng)一遍Generator next
      } catch (e) {
        return reject(e);
        //一有錯(cuò)誤的話就拋出錯(cuò)誤轉(zhuǎn)向rejected
      }
      // 初始化即將第一次yield的·值·傳給next
      next(ret);
      //將這個(gè)指針對象轉(zhuǎn)交next函數(shù)處理
      // 實(shí)現(xiàn)自動(dòng)化的關(guān)鍵
      return null;
    }
    function onRejected(err) {
      //接受error錯(cuò)誤
      var ret;
      //這塊其實(shí)就是處理整個(gè)流程的錯(cuò)誤控制
      try {
        ret = gen.throw(err);
        //利用Generator throw錯(cuò)誤給try catch捕獲
      } catch (e) {
        return reject(e);
        //使得Promise進(jìn)入rejected
      }
      next(ret);
    }
    function next(ret) {
      //接受指針對象

      if (ret.done) return resolve(ret.value);
      //顯示對ret指針狀態(tài)做判斷,done為true證明generator已經(jīng)結(jié)束
      //此時(shí)進(jìn)入resolved結(jié)束整個(gè)Generator
      var value = toPromise.call(ctx, ret.value);
      //將yield 的值進(jìn)行Promise轉(zhuǎn)換

      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      //value在我們允許的范圍內(nèi),那么value.then注入onFulfilled與onRejected,來執(zhí)行下一次gen.next。
      //在onFulfilled又將調(diào)用next從而使得next不停的利用then做調(diào)用
      //如果值是存在并且可以進(jìn)行promise的轉(zhuǎn)換。(也就是不是基本類型/或假值)
      return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, "
        + "but the following object was passed: "" + String(ret.value) + """));
      //如果沒有經(jīng)過值轉(zhuǎn)換或者value為空的時(shí)候。此時(shí)將拋出錯(cuò)誤。
      //因?yàn)槟蔷褪撬^的基本類型不支持了
      //function, promise, generator, array, or object只支持這幾種的
    }
  });
}
//注意我們就只允許這幾種類型轉(zhuǎn)換。
//那么進(jìn)入判斷的時(shí)候我們就可以很簡單地判斷了,然后決定promise的狀態(tài)
function toPromise(obj) {
  if (!obj) return obj;
  //如果obj undefined 或者別的假值返回這個(gè)undefined
  if (isPromise(obj)) return obj;
  //如果是個(gè)Promise的話就返回這個(gè)值
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  //判斷是不是Generator function是的話用co處理
  if ("function" == typeof obj) return thunkToPromise.call(this, obj);
  //如果是函數(shù)的話,使用thunk to promise轉(zhuǎn)換
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  //如果是數(shù)組 使用array to promise
  if (isObject(obj)) return objectToPromise.call(this, obj);
  //如果是對象 使用object to promise 轉(zhuǎn)換
  return obj;
  //如果都不是 就返回`值`
}
// co關(guān)于yield后邊的值也是有一定的要求的,只能是一個(gè) Function|Promise|Generator|Generator Function | Array | Object;
// 而 yield Array和Object中的item也必須是  Function|Promise|Generator | Array | Object;
// 如果不符合的話就將Promise rejected掉并發(fā)出警告

//下面是一些工具函數(shù)

//使用thunk后的fnction 我們只允許它有一個(gè)參數(shù)callbak
//允許有多個(gè)參數(shù) 第一個(gè)參數(shù)為error
//在node環(huán)境下 第一個(gè)為error對象
function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}
// thunkToPromise傳入一個(gè)thunk函數(shù)
// 函數(shù)返回一個(gè)Promise對象
// promise里面執(zhí)行這個(gè)函數(shù)
// nodejs的回調(diào)函數(shù) 第一個(gè)參數(shù)都是err
// 如果有錯(cuò)誤就進(jìn)入rejected(前面我們可以看到 value.then(onFulfilled, onRejected); )
// 如果有error就rejected了
// 如果沒有的話就調(diào)用resolve( 后面onFulfilled )


//將數(shù)組中的所有值均promise化后執(zhí)行,Promise.all會(huì)等待數(shù)組內(nèi)所有promise均fulfilled、或者有一個(gè)rejected,才會(huì)執(zhí)行其后的then。
//對一些基本類型 例如數(shù)字 字符串之類的,是不會(huì)被toPromise轉(zhuǎn)換的
//最后在resolve(res)的時(shí)候 res就是存有所有異步操作執(zhí)行完的值數(shù)組
function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
//對象通過key進(jìn)行遍歷,
//對于每個(gè)被promise化好的value
//都將其存儲(chǔ)于promises中,最后Promise.all,
//生成results。
//objectToPromise實(shí)現(xiàn)實(shí)在是太可怕了=-=
//所以很多字企圖把它講順了
function objectToPromise(obj){
  var results = new obj.constructor();
  var keys = Object.keys(obj);
  var promises = [];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var promise = toPromise.call(this, obj[key]);
    // 確保obj[key]為promise對象
    // 然后調(diào)用defer推入 promises等待value的promise resolved之后將key放入results
    // 否則直接將 results[key] = obj[key](也就是無須promise化的)
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
// 利用promise.all來使用異步并行調(diào)用我們的promises
// 如果執(zhí)行后進(jìn)入resolved然后壓入results對象
// 最后當(dāng)然是返回這個(gè)results對象
// 然后后面的then在獲得時(shí)候 onFulfilled onRejected的參數(shù)將是這個(gè)results
// 這樣子我們每個(gè)promise的結(jié)果都會(huì)存在result對象對應(yīng)的key內(nèi)
// 返回的是一個(gè)promise 后面也就可以接著.then(onFulfilled)
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // predefine the key in the result
    results[key] = undefined;
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}

//檢查是不是promise
//·鴨子類型·判斷。
function isPromise(obj) {
  return "function" == typeof obj.then;
}
//判斷是不是Generator迭代器
function isGenerator(obj) {
  return "function" == typeof obj.next && "function" == typeof obj.throw;
}
 //判斷是不是generator函數(shù)
function isGeneratorFunction(obj) {
  var constructor = obj.constructor;
  if (!constructor) return false;
  if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true;
  return isGenerator(constructor.prototype);
}
//判斷是不是對象
//plain object是指用JSON形式定義的普通對象或者new Object()創(chuàng)建的簡單對象
function isObject(val) {
  return Object == val.constructor;
}

co大概就是干的,將generator自動(dòng)化,更好的將異步流轉(zhuǎn)換同步寫法

ES7的async await
其實(shí)就是generator的語法糖 再加上一個(gè)內(nèi)置自動(dòng)執(zhí)行的混合體
也就是究極體
await的返回值是一個(gè)promise

是不是很像co包裹的generator

參考內(nèi)容:
阮一峰網(wǎng)絡(luò)日志
co 源碼分析 co 與 co.wrap
co 源碼分析

結(jié)語

有兩種方法可以使Generator自動(dòng)化,thunk與Promise

Generator自動(dòng)化可以使得我們的異步代碼編寫得更像同步代碼(回調(diào)地獄是在太可怕了)

涉及異步的,如今很多都是利用Promise,所以掌握Promise是很重要的

希望閱讀此文的人可以有一些收獲。如果有什么錯(cuò)誤的地方也希望可以談出來或者私信我,一起探討。
渴望成長。^v^

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84480.html

相關(guān)文章

  • 秒殺 tj/co 的 hprose 協(xié)程庫

    摘要:而這對于回調(diào)函數(shù)只有一個(gè)返回值參數(shù)的函數(shù),或者回調(diào)函數(shù)的第一個(gè)參數(shù)不表示錯(cuò)誤的函數(shù)來說,庫就無法使用了。當(dāng)對函數(shù)進(jìn)行時(shí),如果回調(diào)函數(shù)第一個(gè)參數(shù)是類型的對象才會(huì)被當(dāng)做錯(cuò)誤處理。因此,第二個(gè)回合,仍然是完勝和。 ES6 中引入了 Generator,Generator 通過封裝之后,可以作為協(xié)程來進(jìn)行使用。 其中對 Generator 封裝最為著名的當(dāng)屬 tj/co,但是 tj/co 跟 ...

    EddieChan 評論0 收藏0
  • 再讀Generator和Co源碼

    摘要:沿用上面的例子,把包裝成一個(gè)對象這個(gè)回調(diào)就是等價(jià)于通過在里執(zhí)行回調(diào)函數(shù),獲取到上一步操作的結(jié)果和交回執(zhí)行權(quán),并把值傳遞回函數(shù)內(nèi)部,實(shí)現(xiàn)了遞歸執(zhí)行進(jìn)一步封裝,可以得到以下的代碼遞歸執(zhí)行 以前看過的內(nèi)容,感覺忘得差不多,最近抽空又看了一次,果然書讀百遍其義自見 Generator的執(zhí)行 Generator函數(shù)可以實(shí)現(xiàn)函數(shù)內(nèi)外的數(shù)據(jù)交換和執(zhí)行權(quán)交換。 從第一次調(diào)用next開始,從函數(shù)頭部開始...

    ernest.wang 評論0 收藏0
  • 理解async

    摘要:寫在前面本文將要實(shí)現(xiàn)一個(gè)順序讀取文件的最優(yōu)方法,實(shí)現(xiàn)方式從最古老的回調(diào)方式到目前的,也會(huì)與大家分享下本人對于庫與庫的理解。其實(shí)的任何異步編程的解決方案的目標(biāo)都是要達(dá)到同步的語義,異步的執(zhí)行。 寫在前面 本文將要實(shí)現(xiàn)一個(gè)順序讀取文件的最優(yōu)方法,實(shí)現(xiàn)方式從最古老的回調(diào)方式到目前的async,也會(huì)與大家分享下本人對于thunk庫與co庫的理解。實(shí)現(xiàn)的效果:順序讀取出a.txt與b.txt,將...

    Jackwoo 評論0 收藏0
  • 理解thunk函數(shù)的作用及co的實(shí)現(xiàn)

    摘要:從形式上將函數(shù)的執(zhí)行部分和回調(diào)部分分開,這樣我們就可以在一個(gè)地方執(zhí)行執(zhí)行函數(shù),在另一個(gè)地方執(zhí)行回調(diào)函數(shù)。這樣做的價(jià)值就在于,在做異步操作的時(shí)候,我們只需要知道回調(diào)函數(shù)執(zhí)行的順序和嵌套關(guān)系,就能按順序取得執(zhí)行函數(shù)的結(jié)果。 thunk thunk 從形式上將函數(shù)的執(zhí)行部分和回調(diào)部分分開,這樣我們就可以在一個(gè)地方執(zhí)行執(zhí)行函數(shù),在另一個(gè)地方執(zhí)行回調(diào)函數(shù)。這樣做的價(jià)值就在于,在做異步操作的時(shí)候,...

    張巨偉 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<