2013-07-20 13 views
31

私はこれを見た。Chaining an arbitrary number of promises in Q;私の質問は違う。可変数の約束をQで順番に連結する方法は?

どのように非同期的に戻ってくる可変数の呼び出しを、順番どおりに行うことができますか?
シナリオは一連のHTTP要求で、その数とタイプは最初のHTTP要求の結果によって決まります。

私はこれを簡単にしたいと思います。

また、私はこのような何かを示唆this answer見てきました:

var q = require('q'), 
    itemsToProcess = ["one", "two", "three", "four", "five"]; 

function getDeferredResult(prevResult) { 
    return (function (someResult) { 
    var deferred = q.defer(); 
    // any async function (setTimeout for now will do, $.ajax() later) 
    setTimeout(function() { 
     var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0]; 
     itemsToProcess = itemsToProcess.splice(1); 
     console.log("tick", nextResult, "Array:", itemsToProcess); 
     deferred.resolve(nextResult); 
    }, 600); 

    return deferred.promise; 
    }(prevResult)); 
} 

var chain = q.resolve("start"); 
for (var i = itemsToProcess.length; i > 0; i--) { 
    chain = chain.then(getDeferredResult); 
} 

を...しかし、それはそのようにitemsToProcessをループに厄介なようです。あるいは、再帰を抽象化する "loop"という新しい関数を定義することもできます。より良い方法は何ですか?

+0

これは、約束の可変数ではないようですが、配列の長さに応じて設定された数です。あなたが実際に可変数を持っているケースがあります。そして、私は 'reduce'メソッドがそれらのために働くかどうか分かりません。 – hippietrail

+0

https://github.com/kriskowal/q#sequencesは正式な参照になります – Aides

答えて

74

[].reduceとこれにする素敵なきれいな方法があります。

var chain = itemsToProcess.reduce(function (previous, item) { 
    return previous.then(function (previousValue) { 
     // do what you want with previous value 
     // return your async operation 
     return Q.delay(100); 
    }) 
}, Q.resolve(/* set the first "previousValue" here */)); 

chain.then(function (lastResult) { 
    // ... 
}); 

reduceは、以前の反復の戻り値を渡して配列を反復処理します。この場合、あなたは約束を返すので、連鎖するたびにthenが連鎖しています。あなたは物事を蹴るために(q.resolve("start")と同じように)最初の約束を提供します。

最初は、ここで起こっていることを頭で囲むのに時間がかかることがありますが、それを処理するには少し時間がかかると、機械をセットアップすることなくどこでも簡単に使用できます。

+10

これはf ****心の曲です。でもまだ素晴らしい;) – manu

+0

甘い!非常に便利です! – frequent

+0

約束が拒否された場合はどうなりますか?あるいは、少なくとも、私たちはそのケースをどのように扱うだろうか? – naivedeveloper

1

は、私はこの方法の方が好き:ここ

var q = require('q'), 
    itemsToProcess = ["one", "two", "three", "four", "five"]; 

function getDeferredResult(a) { 
    return (function (items) { 
    var deferred; 

    // end 
    if (items.length === 0) { 
     return q.resolve(true); 
    } 

    deferred = q.defer(); 

    // any async function (setTimeout for now will do, $.ajax() later) 
    setTimeout(function() { 
     var a = items[0]; 
     console.log(a); 
     // pop one item off the array of workitems 
     deferred.resolve(items.splice(1)); 
    }, 600); 

    return deferred.promise.then(getDeferredResult); 
    }(a)); 
} 

q.resolve(itemsToProcess) 
    .then(getDeferredResult); 

キーは、作業項目の配列のスプライスされたバージョンでdeferred.promise.then()を呼び出すことです。このthenは、最初の延期された約束が解決した後に実行されます。これは、setTimeoutのfnにあります。より現実的なシナリオでは、延期された約束はhttpクライアントのコールバックで解決されます。

最初のq.resolve(itemsToProcess)は、作業項目を作業項目fnの最初の呼び出しに渡すことで作業を開始します。

他の人に役立つことを期待してこれを追加しました。

+1

再帰的ではなく、最初の例のようにループ内で '.then()'チェーンを構築する方が普通です。どちらの方法を選んでも、元のアレイを破壊しないことが重要です。そうであれば、元の無差別配列で動作するインデックス値が増加することで、約束事(およびそのコールバック)を処理できます。 –

1

ここには、Qで定義された状態マシンの概念があります。

は、それがQ約束オブジェクトを返しますので、あなたが、定義されたHTTP機能を持っていると仮定します。

var Q_http = function (url, options) { 
    return Q.when($.ajax(url, options)); 
} 

あなたは次のように再帰関数nextStateを定義することができます。

function process(current, result)はする機能です
var states = [...]; // an array of states in the system. 

// this is a state machine to control what url to get data from 
// at the current state 
function nextState(current) { 
    if (is_terminal_state(current)) 
    return Q(true); 

    return Q_http(current.url, current.data).then(function (result) { 
    var next = process(current, result); 
    return nextState(next); 
    }); 
} 

currentの状態とHTTPコールのresultに従って、次のステップが何であるかを調べます。

あなたがそれを使用し、それが好きで使用します。

nextState(initial).then(function() { 
    // all requests are successful. 
}, function (reason) { 
    // for some unexpected reason the request sequence fails in the middle. 
}); 
+0

これは、他のwhileループソリューションよりもずっと理にかなっています。他のPromiseライブラリにも適応するのが簡単です。 – Seth

1

私は他の解決策を提案します。これはわかりやすいものです。あなたが直接約束を連鎖する場合と同様に あなたが同じことを行います。 promise.then(doSomethingFunction).then(doAnotherThingFunction);

我々はループに、我々はこの取得することを置く場合:私たちは、複数の引数を使用するようにfunction curryingを使用

var chain = Q.when(); 
for(...) { 
    chain = chain.then(functionToCall.bind(this, arg1, arg2)); 
}; 
chain.then(function() { 
    console.log("whole chain resolved"); 
}); 


var functionToCall = function(arg1, arg2, resultFromPreviousPromise) { 
} 

が。この例では、 functionToCall.bind(this, arg1, arg2)は、1つの引数を持つ関数を返します。functionToCall(resultFromPreviousPromise) 以前の約束の結果を使用する必要はありません。

関連する問題