Step.js使用教程(ソース解析付き)


Step.js(https://github.com/creationix/step)は、コントロールフローツール(サイズが150行しかないコード)であり、入れ子レベルが多すぎるなどの問題を解決します.ファイルを読んだり、データベースを調べたりといったコールバック関数が相互に依存したり、コンテンツの最後の組み合わせのデータをそれぞれ取得して返したりするアプリケーションシーンに適用されます.非同期実行は簡単に「シリアル実行」と「パラレル」に分けて実行できます.
シリアル実行
このライブラリは一つの方法しかありません.Step(fns...)Step方法そのパラメータfnsは、複数の関数であり、これらの関数は順次実行されます.Stepワークフローは、thisオブジェクトポインタでカプセル化されます.
Step(
  function readSelf() {
    fs.readFile(__filename, this); //   this    readFile       。   this      function
    //        return    
  },
  function capitalize(err, text) { // err      ,        
    if (err) throw err;
    return text.toUpperCase();     // text        readFile          。              。
  },
  function showIt(err, newText) {
    if (err) throw err;
    console.log(newText);
  }
);
反転関数は、前の関数の結果に依存して、順に実行されます.Stepの一つの約束で、コールバック関数の一番目のパラメータはいつもerrで、二つ目が値です.前のステップに異常が発生した場合、異常オブジェクトは次のステップに送られます.だから私たちはチェックすべきです. errは意味があるかどうか、もし エラが存在する値は、エラー情報を処理するために異常を投げ、以下の論理を実行するのを中止します.もし err nullまたはundefinedであれば、第二のパラメータを意味します.
なぜみんなはリセットと無リターンの違いに注意しますか?これはStepの考えを明らかにします.同期の仕事なら、直接returnでいいです.非同期が必要な場合は、オブジェクトポインタthisを呼び出します.このときthisは関数タイプのオブジェクトです.
前の例のようにfn 次のfnは、「シリアル実行」と呼ばれる実行を行う.
並行して実行する
パラレル実行とは、すべてのコールバックの結果を待って、すべての結果を呼び出し順に配列し、パラメータとして最後の関数に渡す処理です.コールバック関数を組み立てた結果、この方法は上記の依存タンデムとは違って、すべてのコールバックは並行して実行されます.最後のコールバックの結果、最終処理関数を呼び出します.
並行して実行するとPromiseモードに近いwhenシーンがあります. AND"の論理関係は、当事者が実行します.
Step(
  //          Loads two files in parallel
  function loadStuff() {
    fs.readFile(__filename, this.parallel());
    fs.readFile("/etc/passwd", this.parallel());
  },
  //      Show the result when done
  function showStuff(err, code, users) {
    if (err) throw err;
    console.log(code);
    console.log(users);
  }
)
私たちはStep.js APIを使います. 前の例のthisの代わりに提供されたthis.parallel()の方法があります.this.parallelは、私たちの前の例では一回の非同期関数しか生成できない困難を解決し、複数の非同期ステップの配信に適しています.最終的に完成した段階は最後のフィードバックで結果データを収集します.表示されます.呼び出しました n回のthis.parallelを呼び出します. n回の収集resultsの関数.任意の非同期枝に異常が発生した場合、その異常はレスリングに収集されます. において、特に声明を出す. errパラメータでは、タスクの失敗を表し、最後のコールバックは実行されません.
二つのステップの直接の値または異常がどのように伝達されるか、三つのシーンをまとめてみます.
  • thisを使わず、またreturnのいかなる値もなく、ジョブ中断
  • 直接return 値を返します.このとき、非同期プロセスがない場合は
  • が発生します.
  • は、thisを呼び出します.Functionは、非同期プロセスを生成し、この非同期プロセスが完了したら、次のステップ
  • に進みます.
  • 用this.parallel():Functionは、複数の非同期プロセスを生成し、これらの非同期プロセスが完了するのを待って、次のステップ
  • に進みます.
    従って、Step.jsは同期と非同期のタスク組織技術をうまく結合することができる.
    並行実行_高次
    複数の非同期タスクの数が確定されていない場合は、this.group()を使用することができます.
    Step(
      function readDir() {
        fs.readdir(__dirname, this);
      },
      function readFiles(err, results) {
        if (err) throw err;
        //    group,         results   ,    
        var group = this.group();
        results.forEach(function (filename) {
          if (/\.js$/.test(filename)) {
            fs.readFile(__dirname + "/" + filename, 'utf8', group()); // group()        this.parallel,     index/pending。
          }
        });
      },
      function showAll(err , files) {
        if (err) throw err;
        console.dir(files);
      }
    );
    this.groupはthis.parallelと非常に似ています.非同期シーンに使用されます.違いはthis.parallelにあります. 結果は個々のパラメータとして提供されますが、this.groupは結果を配列Arayに統合します.だから、表面的に見ると、それらの違いはfn.call()とfn.apply()の違いに似ています.使用シーンの角度から見ると、両者の本当の違いは、非同期タスクの数をあまり確定しないシーンである.this.groupの別の例を運用して、等価の map():
    function stepMAp(arr, iterator, callback){
      Step(
        function(){
    
        var group = this.group();
        for(var i = 0, j = arr.length; i < j; i++)
          iterator(arr[i], group());
        }),
       callback
    }
    //
    また、グループグループ()(args)などの直接的な呼び出しはよくないので、流れは終了します.
    ソース解析
    実際にはStep.jsの原理は複雑ではなく、主に再帰関数が関数リストを巡回します.非同期タスクが完了したかどうかを計算機でマークします.うまくいっているのは、対象の指針をよく使うことです.
    Step.jsはAsync.jsには及ばないですが(https://github.com/caolan/async)多くの関数を提供しますが、勝利は簡単です.ユーザーは自分で新しい関数をカプセル化して改善していくことができます.筆者はこの観点を認めて、Step.jsを通して、問題を徹底的に考え、優雅で効率的な方案を編み出すことを励ますことができます.
    // Inspired by http://github.com/willconant/flow-js, but reimplemented and
    // modified to fit my taste and the node.JS error handling system.
    function Step() {
      var steps = Array.prototype.slice.call(arguments), //     
          counter, pending, results, lock;               //        
    
      //       Define the main callback that's given as `this` to the steps.
      function next() {
        counter = pending = 0; // counter   pendig          :                  
    
        //           steps Check if there are no steps left
        if (steps.length === 0) {
          //          Throw uncaught errors
          if (arguments[0]) {
            throw arguments[0];
          }
          return;
        }
    
        //          Get the next step to execute
        var fn = steps.shift();
        results = [];
    
        // try...catch      Run the step in a try..catch block so exceptions don't get out of hand.
        try {
          lock = true;
          var result = fn.apply(next, arguments); //    args   next       
        } catch (e) {
          //      ,               Pass any exceptions on through the next callback
          next(e);
        }
    
        if (counter > 0 && pending == 0) { // couter > 0        ,pending == 0         
          //          (     ),              ,       
          // If parallel() was called, and all parallel branches executed
          // synchronously, go on to the next step immediately.
          next.apply(null, results); //         results   。   results           
        } else if (result !== undefined) {
          //      return   (     ),        If a synchronous return is used, pass it to the callback
          next(undefined, result);
        }
        lock = false;
      }
    
      //          Add a special callback generator `this.parallel()` that groups stuff.
      next.parallel = function () {
        var index = 1 + counter++;
        pending++; //            
    
        return function () {
          pending--;//      1,      
          //      ,                Compress the error from any result to the first argument
          if (arguments[0]) {
            results[0] = arguments[0];
          }
          //         Send the other results as arguments
          results[index] = arguments[1];
          if (!lock && pending === 0) {//       
            //         ,        。When all parallel branches done, call the callback
            next.apply(null, results);
          }
        };
      };
    
      // Generates a callback generator for grouped results
      next.group = function () {
        var localCallback = next.parallel();
        var counter = 0;
        var pending = 0;
        var result = [];
        var error = undefined;
    
        function check() {
          if (pending === 0) {
            // When group is done, call the callback
            localCallback(error, result);
          }
        }
        process.nextTick(check); // Ensures that check is called at least once
    
        // Generates a callback for the group
        return function () {
          var index = counter++;
          pending++;
          return function () {
            pending--;
            // Compress the error from any result to the first argument
            if (arguments[0]) {
              error = arguments[0];
            }
            // Send the other results as arguments
            result[index] = arguments[1];
            if (!lock) { check(); }
          };
        };
      };
    
      //       Start the engine an pass nothing to the first step.
      next();
    }
    
    /**
    
                  
    */
    // Tack on leading and tailing steps for input and output and return
    // the whole thing as a function. Basically turns step calls into function
    // factories.
    Step.fn = function StepFn() {
      var steps = Array.prototype.slice.call(arguments);
      return function () {
        var args = Array.prototype.slice.call(arguments);
    
        // Insert a first step that primes the data stream
        var toRun = [function () {
          this.apply(null, args);
        }].concat(steps);
    
        //         If the last arg is a function add it as a last step
        if (typeof args[args.length-1] === 'function') {
          toRun.push(args.pop());
        }
    
    
        Step.apply(null, toRun);
      }
    }
    
    
    // CommonJS        Hook into commonJS module systems
    if (typeof module !== 'undefined' && "exports" in module) {
      module.exports = Step;
    }
    
    私はsea.jsパッケージのメカニズムに適応するために、define()の呼び出しを入れます.http://naturaljs.googlecode.com/svn/trunk/libs/step.js
    Step.jsも他の人のコードを参考にしました.https://github.com/willconant/flow-js巨人の肩に立ちますよ.同じタイプのオープンソースライブラリを参照してください:
  • https://github.com/isaacs/slide-flow-control
  • 国産Step.js:https://github.com/myworld4059/Step.js/blob/master/Step.js
  • 簡単なテストコード
    Step(function(a){
    	throw 'err';
    	return 1;
    }, function(err, b){
    	console.log(err);
    	return [b, 2];
    }, function(err, c){
    	console.log(err);
    	alert(c);
    });
    Step欠点はtry...catchを使って業務コードを包み、異常を全て捕獲し、調整段階では友好的ではない.どうやって破れますか?調整器を「Pause on all exception」に設定できます.