coモジュールの使い方と分析

7283 ワード

この記事は個人ブログとSegmentFaultコミュニティの個人欄だけで発表します.転載は出典の個人ブログを明記してください.https://zengxiaotao.github.io SegmentFault個人欄:https://segmentfault.com/blog...
前に書く
nodejsを学んでもちろん学習の枠組みを免れられないで、結局原生のAPIは比較的に低いです.最初に接触したのはKKAです.公式サイトの説明を見てください.
next generation web frame ework for node.js
node.jsに基づく次世代web開発の枠組みです.すごいようです.koaは軽量級のフレームであり、本質的には棚を提供しており、様々な中間部品のカスケード接続によって特定の機能を実現しています.koaはpromiseとgeneratorに助けを借りて、非同期の組み合わせの問題をよく解決しました.
あれは何ですか?koaを勉強するには、coモジュールを勉強する必要があります.coモジュールは非同期をリリースできます.co関数は一つのgenerator関数をパラメータとして受け入れ、関数内部で自動的にyieldを実行します.
ソース分析
使用するcoモジュールのバージョン番号は4.6.0です.
まず、オブジェクトの種類を判断するための関数を見ます.
var slice = Array.prototype.slice; //     slice       
function isObject(val) {
  return Object == val.constructor;
}
この二つは言うまでもないでしょう.
function isPromise(obj) {
  return 'function' == typeof obj.then;
}
対象がpromiseの例かどうかを判断し、判断する根拠も簡単です.アヒルの種類によって、この対象がthenの方法があるかどうかを判断します.
function isGenerator(obj) {
  return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
同様に、一つのオブジェクトを判断する場合は、nextメソッドと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);
}
generator関数かどうかを判断するには、この関数がGeneratorFunction関数の例であるかどうかを判断するだけでよい.
上記のように、今後valueをプロミケースに包装する時に使います.
coモジュールの出力部分を見てください.
module.exports = co['default'] = co.co = co
したがって、以下の3つの用法は等価である.
var co = require('co') // (1)
var co = require('co').co // (2)
var co = require('co').default // (3)
続いて、ヘッディングco関数です.
function co(gen) {
  var ctx = this; //             
  var args = slice.call(arguments, 1) //    gen      
  //      promise   
  return new Promise(function(resolve, reject) {
       //       generator        generator   
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    //       gen      generator   , 
    // promise      resolved   
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
     //    onFulfilled   
    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        //    gen   next   
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      //         next   
      next(ret);
    }

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

    function next(ret) {
      //    gen     , ret.done    true ,     promise    
      //          resolved
      if (ret.done) return resolve(ret.value); 
      var value = toPromise.call(ctx, ret.value); //   value         promise   
      //      promise     resolve       onFulfilled   ,     next   ,           generator     next   
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);      
      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モジュールは、GEneratorのインスタンスを自動的に実行するnext方法を実現しました.次に、coはどのように一つの値をプロミックスに変換しますか?
function toPromise(obj) {
  if (!obj) return obj;  //       obj    ,         undefined , false, null
  if (isPromise(obj)) return obj; //     Promise   ,     promise   
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); //     generator        generator
  if ('function' == typeof obj) return thunkToPromise.call(this, obj); //     thunk   
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj); //        
  if (isObject(obj)) return objectToPromise.call(this, obj); //       plain object
  return obj; //       ,        。
}
じゃ、各関数を順番に見てください.
function thunkToPromise(fn) {
  var ctx = this; //          
  //      promise   
  return new Promise(function (resolve, reject) {
    //       thunk   
    // thunk                 
    fn.call(ctx, function (err, res) {
        //    thunk       
        // promise        rejected   ,   reject   ,    co        onRejected   ,  
      if (err) return reject(err);
          //       
      if (arguments.length > 2) res = slice.call(arguments, 1);
      // promise      resolved ,   resolve   ,    onFulfilled   
      resolve(res);
    });
  });
}
したがって、要約すると、このthunk関数はgenerator yieldの後にthunk関数があるということです.このthunk関数はパラメータとしてフィードバックパラメータを受け入れています.
function arrayToPromise(obj) {
  // Promise.all           promise   
  //    obj      ,           promise   
  //       promise       resolved   
  //         promise         resloved   
  //    resolve            promise            
  return Promise.all(obj.map(toPromise, this)); 
}
同様に、Objが配列である場合、yield文の後の表現の値が配列である場合、Promise.all方法を実行して、配列の各項目をpromiseの例に変えます.
具体的な方法は以下の通りです.
  • toPromise方法を用いて、obj配列の各々を一つのpromise例
  • に包装する.
  • 前の段階の配列に要素が含まれている場合、Promise.all方法はPromise.resove方法を呼び出し、これをpromiseインスタンスに変換する.
  • Promise.all方法は、新たなpromiseの例を返す.
  • promiseの例の配列の中のすべてのインスタンスの状態がresoved状態に変化している場合にのみ、この新しいpromiseのインスタンスの状態がresolovedに変化します.配列の中にpromiseの一例がある状態がrejectiedになると、新しいpromiseのインスタンス状態もすぐにrejectiedに変わります.
  • に戻る新しいpromiseのインスタンス状態がresovedに変化すると、そのresove関数に入ってくるパラメータは、以前の配列の各promiseのインスタンスに対して、resove関数の戻り値からなる配列を呼び出す.戻る新しいpromiseの状態がrejectに変化すると、reject関数に伝えられるパラメータは配列中のpromiseのインスタンスが最初にreject状態に変化する場合、reject関数の戻り値を実行します.
  • 本当に回り道をして、何回も見たら理解できるはずです.
    最後に、もしret.valueが対象なら、coモジュールはどうやってpromiseの例に変えますか?
    function objectToPromise(obj){
      //        
      var results = new obj.constructor();
      //    obj      
      var keys = Object.keys(obj);
      //                  promise   
      var promises = []; 
      for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var promise = toPromise.call(this, obj[key]); //           promise   
        if (promise && isPromise(promise)) defer(promise, key); 
        else results[key] = obj[key];
      }
      //      promise.all           
      return Promise.all(promises).then(function () {
        return results; //   results    onFulfilled      
      });
      //      
      //   promise    resolve   
      //       promise      promises   
      function defer(promise, key) {
        // predefine the key in the result
        results[key] = undefined;
    
        promises.push(promise.then(function (res) {
          results[key] = res; //   promise     resolve   
        }));
      }
    }
    締め括りをつける
    分析し終わったcoのソースコードの全体の実行過程をまとめます.まず、co関数は一つのgenerator関数を受け取り、co関数の内部で実行され、一つのgeneratorのインスタンスを生成する.generatorのnextメソッドを呼び出して、生成されたオブジェクトのvalue属性値にtoPromise方法を使用して、promiseの例を生成し、このpromiseのインスタンスの状態がresolivedに変化した場合、one Fulfilled方法を実行し、再びgeneratorのインスタンスにnext方法を実行し、その後、プロセス全体を繰り返す.エラーが発生した場合、このpromiseのインスタンスによって定義されたreject関数すなわちonReject方法を実行します.
    以上は非同期プロセスを同期化することを実現した.
    最後にstarを歓迎します
    https://github.com/zengxiaotao/zengxiaotao.github.io