Async詳細解の一つ:プロセス制御

10895 ワード

From: http://freewind.me/blog/20120515/917.html
Async詳細解の一つ:プロセス制御
2012年5月15日
非同期プログラミングに適応するために、リピーターの入れ子を減らすために、多くのライブラリを試しました.やはりasyncが一番頼りだと思います.
住所:https://github.com/caolan/async
Asyncの内容は三つの部分に分けられます.
フロー制御:十種の共通の流れを簡略化する処理集合処理:非同期操作を使用してセット内のデータを処理する方法工具類:いくつかの常用工具類本論文では、最も簡単で一般的なフロー制御部分を紹介します.
nodejsは非同期プログラミングモデルなので、同期プログラミングで簡単にできることがありますが、今は大変です.Asyncのフロー制御はこれらの操作を簡略化するためです.
1.series(taskys,[calback])(複数の関数が順次実行され、データ交換が行われていない)
複数の非同期関数が順次呼び出され、一つの完了後に次のステップが実行されます.各関数間にはデータの交換がありません.順序を保証するだけです.この場合はseriesが使えます.
純jsコード:
step1(function(err, v1) {
  step2(function(err, v2) {
    step3(function(err, v3) {
       // do somethig with the err or values v1/v2/v3
    }
  }
});
その中からこのネストが見られますか?それとも比較的深いものですか?もう少し多く歩けばもっと深いです.コードの中で無視して、各レイヤーのerrの処理をおろそかにして、さもなくばまたif return calbackをプラスして、それでは更に面倒になりました.
この場合、asyncを使って処理するということです.
var async = require('async')
async.series([
   step1, step2, step3
], function(err, values) {
   // do somethig with the err or values v1/v2/v3
});
コードが多く簡潔であり、各コールバックのエラーを自動的に処理することができます.もちろん、ここで一番簡単な例だけをあげます.実際には、各ステップでいくつかの操作を行います.
var async = require('async')
async.series([
  function(cb) { step1(function(err,v1) {
     // do something with v1
     cb(err, v1);
  }),
  function(cb) { step2(...) },
  function(cb) { step3(...) }
], function(err, values) {
// do somethig with the err or values v1/v2/v3
});
この関数の詳細は以下の通りです.
関数配列の各関数を順次実行します.各関数の実行が完了してから次の関数が実行されます.任意の関数がそのコールバック関数に一つのerrorを伝えたら、後の関数は実行されず、すぐにこのerrorと実行された関数の結果をseriesの最後のcalbackに伝えます.すべての関数が実行された後(エラーはありません)、各関数がそのコールバック関数に伝えられた結果を一つの配列に統合して、seriesの最後のcalbackに伝えます.また、jsonの形式でtaskysを提供することもできます.各属性は関数として実行されます.結果はjson形式でseriesの最後のcalbackにも伝えられます.このような方式のほうが可読性が高いです.具体例は参照できます.https://github.com/freewind/async_demo/blob/master/series.js
そのコードには、次のようなものも含まれています.
中間の関数が間違っていたら、series関数はどう処理しますか?
ある関数がコールバックに伝達された値がundefined、null、{}、[]などであれば、seriesはをどのように処理しますか?
また、注意が必要なのは、複数のseriesの呼び出しの間には前後の区別がなく、series自体も非同期であるからです.
2.parallel(tasks,[calback])(複数の関数を並列に実行)
複数の関数を並行して実行します.各関数は直ちに実行されます.他の関数が先に実行されるのを待つ必要はありません.最終的なcalbackに送られる配列のデータは、完了した順序ではなくtaskで宣言された順序に従います.
ある関数が間違っていたら、すぐにerrと実行済みの関数の結果値をparallelの最終的なcalbackに伝えます.他の実行されていない関数の値は最終データには届きませんが、位置を占めます.
json形式のtaskysをサポートし、最終的にlbackをcalbackした結果もJson形式です.
サンプルコード:
async.parallel([ 
    function(cb) { t.fire('a400', cb, 400) }, 
    function(cb) { t.fire('a200', cb, 200) }, 
    function(cb) { t.fire('a300', cb, 300) } 
], function (err, results) { 
    log('1.1 err: ', err); // -> undefined 
    log('1.1 results: ', results); // ->[ 'a400', 'a200', 'a300' ] 
});
途中エラーの例:
async.parallel([ 
    function(cb) { log('1.2.1: ', 'start'); t.fire('a400', cb, 400) }, //            callback,       
    function(cb) { log('1.2.2: ', 'start'); t.err('e200', cb, 200) }, 
    function(cb) { log('1.2.3: ', 'start'); t.fire('a100', cb, 100) } 
], function(err, results) { 
    log('1.2 err: ', err); // -> e200 
    log('1.2 results: ', results); // -> [ , undefined, 'a100' ] 
});
json形式でtaskysに伝えられました.
async.parallel({ 
    a: function(cb) { t.fire('a400', cb, 400) }, 
    b: function(cb) { t.fire('c300', cb, 300) } 
}, function(err, results) { 
    log('1.3 err: ', err); // -> undefined 
    log('1.3 results: ', results); // -> { b: 'c300', a: 'a400' } 
});
より詳細な例を参照してください.https://github.com/freewind/async_demo/blob/master/parallel.js
3.waterfall(taskys、[calback])(複数の関数が順次実行され、前の出力が次の入力となります.)
seiresと同様に、複数の関数を順次実行します.それぞれの関数で生成された値は、次の関数に伝えられます.途中でエラーが発生したら、後の関数は実行されません.エラーメッセージと以前に発生した結果は、waterfallの最終的なcalbackに伝えます.
この関数はwaterfallといい、滝が上から下にかけて、途中でいくつもの突起がある石を突き進んでいくのが想像できます.なお、この関数はjson形式のtaskをサポートしていません.
async.waterfall([ 
    function(cb) { log('1.1.1: ', 'start'); cb(null, 3); }, 
    function(n, cb) { log('1.1.2: ',n); t.inc(n, cb); }, 
    function(n, cb) { log('1.1.3: ',n); t.fire(n*n, cb); } 
], function (err, result) { 
    log('1.1 err: ', err); // -> null 
    log('1.1 result: ', result); // -> 16 
});
より詳細な例を参照してください.https://github.com/freewind/async_demo/blob/master/waterfall.js
4.aut(task,[calback])(複数の関数に依存関係があり、並列実行もあり、順次実行もあります.)
依存関係のある複数のタスクを処理するために使用されます.例えば、いくつかのタスクの間で独立して、並行して実行できます.しかし、いくつかのタスクは他のいくつかのタスクに依存しています.それらのタスクが完了するまで待つしかないです.
私たちはasync.parallelとasync.seriesを使ってこの機能を実現することができますが、ミッション間の関係が複雑であれば、コードはかなり複雑になります.これからは新しいタスクを追加したいなら、面倒です.この時にasync.atotを使うと、それは半ば功を奏します.
ミッションの途中でエラーが発生したら、そのエラーを最後のcalbackに伝えます.実行済みのデータも含めて全てのタスクが無視されます.
ここでもし私がプログラムを書くと、次のようなことができます.
某所からデータを取得するハードディスクに新しいディレクトリを作成します.ディレクトリの下にデータを書き込むメールを送ります.ファイルを添付の形で他の人に送ります.このタスクを分析すると、1と2が並行して実行できます.3は1と2が必要です.4は3が必要です.
async.auto({ 
    getData: function (callback) { 
        setTimeout(function(){ 
            console.log('1.1: got data'); 
            callback(); 
        }, 300); 
    }, 
    makeFolder: function (callback) { 
        setTimeout(function(){ 
            console.log('1.1: made folder'); 
            callback(); 
        }, 200); 
    }, 
    writeFile: ['getData', 'makeFolder', function(callback) { 
        setTimeout(function(){ 
            console.log('1.1: wrote file'); 
            callback(null, 'myfile'); 
        }, 300); 
    }], 
    emailFiles: ['writeFile', function(callback, results) { 
        log('1.1: emailed file: ', results.writeFile); // -> myfile 
        callback(null, results.writeFile); 
    }] 
}, function(err, results) { 
    log('1.1: err: ', err); // -> null 
    log('1.1: results: ', results); // -> { makeFolder: undefined, 
                                    //      getData: undefined, 
                                    //      writeFile: 'myfile', 
                                    //      emailFiles: 'myfile' } 
});
詳細例を参照してください.https://github.com/freewind/async_demo/blob/master/aut.js
5.whilst(test,fn,calback)(非同期で呼び出すことができるwhile)
whileに相当しますが、その中の非同期呼び出しは完了後に次のサイクルが行われます.以下の例を示します
var count1 = 0; 
async.whilst( 
    function() { return count1 < 3 }, 
    function(cb) { 
        log('1.1 count: ', count1); 
        count1++; 
        setTimeout(cb, 1000); 
    }, 
    function(err) { 
        // 3s have passed 
        log('1.1 err: ', err); // -> undefined 
    } 
);
これは以下に相当します
try { 
  whilst(test) { 
    fn(); 
  } 
  callback(); 
} catch (err) { 
  callback(err); 
}
この関数の機能は比較的簡単で、条件変数は通常外部に定義されています.各関数にアクセスできます.サイクルの中で、非同期呼出時に発生した値は実際に破棄されました.最後のcalbackはエラー情報しか入ってこないからです.
さらに、第二の関数fnは、エラーまたは正常終了を表すために、関数cbを受け入れる必要があります.
より詳細な例を参照してください.https://github.com/freewind/async_demo/blob/master/whilst_until.js
6.until(test,fn,calback)(whileと似ていますが、判定条件は逆です.)
var count4 = 0; 
async.until( 
    function() { return count4>3 }, 
    function(cb) { 
        log('1.4 count: ', count4); 
        count4++; 
        setTimeout(cb, 200); 
    }, 
    function(err) { 
        // 4s have passed 
        log('1.4 err: ',err); // -> undefined 
    } 
);
最初の関数条件がfalseである場合は、2番目の関数を実行し続けます.
7.queue(ワーカーの数を設定できるキュー)
queueは強化版のparallelに相当します.主にworkerの数量を制限しています.もう一回で全部実行しません.ワーカーの数が足りない時は、新しいワーカーが利用できるまで並んで待ちます.
この関数には、ワーカーが使い終わった時、待ち時間がない時、全部実行した時など、複数の点があります.
queueを定義します.そのworkerの数は2です.そして、タスク実行時にログを記録してください.
var q = async.queue(function(task, callback) { 
    log('worker is processing task: ', task.name); 
    task.run(callback); 
}, 2);
ウォーカーの数がなくなると、saturated関数が起動されます.
q.saturated = function() { 
    log('all workers to be used'); 
}
最後のタスクをワーカーに実行させると、empy関数が起動されます.
q.empty = function() { 
    log('no more tasks wating'); 
}
すべてのタスクが完了すると、drain関数が起動されます.
q.drain = function() { 
    console.log('all tasks have been processed'); 
}
複数のタスクを入れて、1回に1つ、または1回に複数のタスクを置くことができます.
q.push({name:'t1', run: function(cb){ 
    log('t1 is running, waiting tasks: ', q.length()); 
    t.fire('t1', cb, 400); // 400ms    
}}, function(err) { 
    log('t1 executed'); 
});
q.push([{name:'t3', run: function(cb){ 
    log('t3 is running, waiting tasks: ', q.length()); 
    t.fire('t3', cb, 300); // 300ms    
}},{name:'t4', run: function(cb){ 
    log('t4 is running, waiting tasks: ', q.length()); 
    t.fire('t4', cb, 500); // 500ms    
}}], function(err) { 
    log('t3/4 executed'); 
});
詳細例を参照してください.https://github.com/freewind/async_demo/blob/master/queue.js
8.iterator(taskys)(いくつかの関数をiteratorとして包装する)
一組の関数を一つのiteratorに包装し、next()を通して次の関数を起点とする新しいiteratorを得ることができます.この関数は主にasyncによって内部で使用されますが、必要なときにはコードでも使用できます.
var iter = async.iterator([ 
    function() { console.log('111') }, 
    function() { console.log('222') }, 
    function() { console.log('333') } 
]);
console.log(iter());
console.log(iter.next());
直接に()を呼び出すと、現在の関数が実行され、次の関数から始点となる新しいiteratorが返されます.next()を呼び出しても、現在の関数は実行されません.次の関数から出発点となる新しいiteratorに直接戻ります.
同じiteratorに対して、next()を何度も呼び出しても、自分に影響はありません.要素が一つしか残っていない場合は、next()を呼び出してnullに戻ります.
より詳細な例を参照してください.https://github.com/freewind/async_demo/blob/master/iterator.js
9.apply(function,argments.)(関数へのプリバインディングパラメータ)
applyは非常に有用な関数であり、関数に複数のパラメータをプリバインし、直接に呼び出すことができる新しい関数を生成し、コードを簡略化することができます.
関数について:
function(callback) { t.inc(3, callback); }
appyで書き換えることができます.
async.apply(t.inc, 3);
いくつかの関数のプリセット値も与えられます.新しい関数が得られます.
var log = async.apply(console.log, ">");
log('hello'); 
// > hello
詳細コード参照:https://github.com/freewind/async_demo/blob/master/appy.js
10.nextTick(calback)
nextTickの役割はnodejsのnextTickと同じで、ある関数を呼び出して列の最後に置くのです.ただし、ブラウザ側では、setTimeout(calback,0)しか使えませんが、この方法は他の優先度の高いジョブを先に挿入する場合があります.
このnextTickを提供して、同じコードをサーバー側とブラウザ側で表現させます.
var calls = [];
async.nextTick(function() {
    calls.push('two');
});
calls.push('one');
async.nextTick(function() {
    console.log(calls); // -> [ 'one', 'two' ]
});
詳細コード参照:https://github.com/freewind/async_demo/blob/master/nextTick.js
次はリラックスして、asyncが提供するツール類を説明します.最後に集合の並列処理です.