thunkify与co源码解读

开首

起首本文有快要3000字,浏览能够会占用你20分钟摆布。

文笔能够不佳,愿望能协助到浏览此文的人有一些收成

在举行源码浏览前
起首抱有一个疑问,thunk函数是什么,thunkify库又是干什么的,co又是干吗,它有啥用

程序语言有两种求值战略

传名挪用 传入参数实际上是传入函数体

传值挪用 函数体在进入的时刻就举交运算盘算值

编译器的”传名挪用”完成,往往是将参数放到一个暂时函数当中,再将这个暂时函数传入函数体。这个暂时函数就叫做 Thunk 函数。

在 JavaScript 语言中,Thunk 函数替代的不是表达式,而是多参数函数,将其替代成单参数的版本,且只接收回调函数作为参数

这几句话来自阮一峰先生的blog文章

试想下我们在node环境下要运用fs.readfile

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

而运用thunk简朴革新以后我们的函数能够变成这模样的情势

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

此时挪用readfile的话,我们能够这么挪用

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');
    // 引入断言库推断是不是是函数
    // 返回一个包括thunk函数的匿名函数
    return function () {
        var args = new Array(arguments.length);
        // 建立一个数组空间
        var ctx = this;
        // 猎取上下文环境用于背面绑定上下文

        for (var i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }
        // 迭代传参,由于有内存走漏bug
        // 返回真正的thunk函数
        return function (done) {
            // done相称因而实行后的callback
            var called;
            // 声明一个called保证只实行一次这个回调函数
            // 压入一个数组中举行这类间隔,防备被屡次实行
            args.push(function () {
                if (called) return;
                called = true;
                done.apply(null, arguments);
            });
            // 用try catch 在实行失利也走一次callback 传入err信息
            try {
                fn.apply(ctx, args);
            } catch (err) {
                done(err);
            }
        }
    }
};

代码并不难明
乍一看,这彷佛没什么用吧。

但 js厥后有一个Generator函数,thunk此时似乎有了作用

Generator函数

运用yield 就是将控制权放出停息实行
然后返回一个当前指针(遍历器对象)

所以我们是不是须要有一种要领接收而且能够继承返回这类控制权
显式的挪用next当然没有问题。然则我们要自动的话?该怎么办

基于自动流程治理,我们应用thunk函数的特征,挪用回调函数callback
回调函数内里递归挪用generator的next要领
直到状况值为done generator函数完毕
这时刻全部generator就能够很文雅地被处置惩罚

然后我们设想,这个流程thunk函数能够干什么

主要的功用实际上是经由过程封装多层使得我们能够在回调函数内取得控制权
返回控制权
由于平常根据一般写法
我们须要显式地挪用next next来使得我们的Generator一步步完成
那末我们只须要一种机制,能够协助我们取得控制权,而且返回控制权
都能够完成自动化

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

这是一个简朴的demo应用thunkify完成自动化generator

thunk函数回调挪用next是一种要领
Pormise的then挪用next 同时也是一种处置惩罚办法
区分在于thunk可控(指的是在回调中我们能够可控实行),promise马上实行

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范例与用法

co源码剖析

var slice = Array.prototype.slice;
module.exports = co['default'] = co.co = co;
co.wrap = function (fn) {
  //兼容有参数的generator函数
  //应用柯里化将generator转换成一般函数
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};
function co(gen) {
  var ctx = this;
  //取得当前上下文环境
  var args = slice.call(arguments, 1);
  //取许多参数(假如有的话)
  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  //会内存走漏
  //返回一个promise相称于将一切都包裹在promise内里。使得我们co返回的能够运用promise的要领
  // co的返回值是Promise对象。为何能够then和catch的泉源
  return new Promise(function(resolve, reject) {
    //做范例的推断。
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    //Generator函数实行以后会是typeof会是对象。
    //默许实行挪用一次Generator返回一个遍历器对象Generator
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    //推断范例 假如不符合  promise就进入resolved
    // 看看是不是是Generator指针
    //传入的不是Generators函数,没有next,
    // 就直接resolve返回效果;这里是毛病兼容罢了,由于co就是基于generator function的,传入其他的没有意义

    //实行onFulfilled
    onFulfilled();
    //返回一个promise

    //onFulfilled干了什么。实在跟我们之前的一样,只是这里触及到了promise的状况。假如出错了。状况返回是reject
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
        //初始化启动一遍Generator next
      } catch (e) {
        return reject(e);
        //一有毛病的话就抛出毛病转向rejected
      }
      // 初始化行将第一次yield的·值·传给next
      next(ret);
      //将这个指针对象转交next函数处置惩罚
      // 完成自动化的症结
      return null;
    }
    function onRejected(err) {
      //接收error毛病
      var ret;
      //这块实在就是处置惩罚全部流程的毛病控制
      try {
        ret = gen.throw(err);
        //应用Generator throw毛病给try catch捕捉
      } catch (e) {
        return reject(e);
        //使得Promise进入rejected
      }
      next(ret);
    }
    function next(ret) {
      //接收指针对象

      if (ret.done) return resolve(ret.value);
      //显现对ret指针状况做推断,done为true证实generator已完毕
      //此时进入resolved完毕全部Generator
      var value = toPromise.call(ctx, ret.value);
      //将yield 的值举行Promise转换

      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      //value在我们许可的范围内,那末value.then注入onFulfilled与onRejected,来实行下一次gen.next。
      //在onFulfilled又将挪用next从而使得next不断的应用then做挪用
      //假如值是存在而且能够举行promise的转换。(也就是不是是基础范例/或假值)
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
      //假如没有经由值转换或许value为空的时刻。此时将抛出毛病。
      //由于那就是所谓的基础范例不支撑了
      //function, promise, generator, array, or object只支撑这几种的
    }
  });
}
//注重我们就只许可这几种范例转换。
//那末进入推断的时刻我们就能够很简朴地推断了,然后决议promise的状况
function toPromise(obj) {
  if (!obj) return obj;
  //假如obj undefined 或许别的假值返回这个undefined
  if (isPromise(obj)) return obj;
  //假如是个Promise的话就返回这个值
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  //推断是不是是Generator function是的话用co处置惩罚
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  //假如是函数的话,运用thunk to promise转换
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  //假如是数组 运用array to promise
  if (isObject(obj)) return objectToPromise.call(this, obj);
  //假如是对象 运用object to promise 转换
  return obj;
  //假如都不是 就返回`值`
}
// co关于yield后边的值也是有肯定的请求的,只能是一个 Function|Promise|Generator|Generator Function | Array | Object;
// 而 yield Array和Object中的item也必需是  Function|Promise|Generator | Array | Object;
// 假如不符合的话就将Promise rejected掉并发出正告

//下面是一些东西函数

//运用thunk后的fnction 我们只许可它有一个参数callbak
//许可有多个参数 第一个参数为error
//在node环境下 第一个为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传入一个thunk函数
// 函数返回一个Promise对象
// promise内里实行这个函数
// nodejs的回调函数 第一个参数都是err
// 假如有毛病就进入rejected(前面我们能够看到 value.then(onFulfilled, onRejected); )
// 假如有error就rejected了
// 假如没有的话就挪用resolve( 背面onFulfilled )


//将数组中的一切值均promise化后实行,Promise.all会守候数组内一切promise均fulfilled、或许有一个rejected,才会实行厥后的then。
//对一些基础范例 比方数字 字符串之类的,是不会被toPromise转换的
//末了在resolve(res)的时刻 res就是存有一切异步操纵实行完的值数组
function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
//对象经由过程key举行遍历,
//关于每一个被promise化好的value
//都将其存储于promises中,末了Promise.all,
//天生results。
//objectToPromise完成实在是太可怕了=-=
//所以许多字希图把它讲顺了
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对象
    // 然后挪用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来运用异步并行挪用我们的promises
// 假如实行后进入resolved然后压入results对象
// 末了当然是返回这个results对象
// 然后背面的then在取得时刻 onFulfilled onRejected的参数将是这个results
// 这模样我们每一个promise的效果都邑存在result对象对应的key内
// 返回的是一个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函数
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()建立的简朴对象
function isObject(val) {
  return Object == val.constructor;
}

co也许就是干的,将generator自动化,更好的将异步流转换同步写法

ES7的async await
实在就是generator的语法糖 再加上一个内置自动实行的混合体
也就是究极体
await的返回值是一个promise

是不是是很像co包裹的generator

参考内容:
阮一峰网络日志
co 源码剖析 co 与 co.wrap
co 源码剖析

结语

  1. 有两种要领能够使Generator自动化,thunk与Promise
  2. Generator自动化能够使得我们的异步代码编写得更像同步代码(回调地狱是在太可怕了)
  3. 触及异步的,现在许多都是应用Promise,所以控制Promise是很主要的

愿望浏览此文的人能够有一些收成。假如有什么毛病的处所也愿望能够谈出来或许私信我,一同讨论。
盼望生长。^v^

    原文作者:ZWkang
    原文地址: https://segmentfault.com/a/1190000010414406
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞