私は自分のコードを promise に再構成し、複数のものからなる、すばらしいlongフラットなpromiseチェーンを構築しました。 .then()
コールバック最後に、私はいくつかの複合値を返したい、そして複数の中間約束結果にアクセスする必要がある。しかし、シーケンスの途中からの解像度の値は、最後のコールバックでは有効範囲にありません。どうやってそれらにアクセスするのですか?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
もちろん、この問題は言語デザイナーによっても認識されていました。彼らは多くの仕事をしました、そして 非同期関数の提案 はついにそれを次のようにしました。
非同期関数(呼び出されたときにpromiseを返す)のように、promiseが直接解決されるのを待つだけでよいので、then
の呼び出しやコールバック関数はもう必要ありません。また、条件、ループ、try-catch-句などの任意の制御構造も備えていますが、便宜上、ここでは必要ありません。
async function getExample() {
var resultA = await promiseA(…);
// some processing
var resultB = await promiseB(…);
// more processing
return // something using both resultA and resultB
}
私たちがES8を待っている間、私たちはすでに非常によく似た種類の構文を使いました。 ES6には ジェネレータ関数 が付属しています。これにより、任意の場所に配置されたyield
キーワードで実行を分割することができます。これらのスライスは、独立して、非同期でさえも、順番に実行することができます - そしてそれは、次のステップを実行する前に、約束の解決を待つときに行うことです。
専用のライブラリ( co や task.js など)がありますが、多くのプロミスライブラリにはヘルパー関数があります( Q 、 Bluebird 、 の場合 、…) あなたが彼らに約束を生み出すジェネレータ関数を与えるとき)あなたのために)この非同期ステップバイステップの実行 。
var getExample = Promise.coroutine(function* () {
// ^^^^^^^^^^^^^^^^^ Bluebird syntax
var resultA = yield promiseA(…);
// some processing
var resultB = yield promiseB(…);
// more processing
return // something using both resultA and resultB
});
これは、バージョン4.0以降のNode.jsで機能しました。また、いくつかのブラウザ(またはそれらの開発版)は、比較的早期にジェネレータ構文をサポートしていました。
しかし、後方互換性が必要な場合は、transpilerなしでは使用できません。ジェネレータ関数と非同期関数は現在のツールでサポートされています。例えば、 ジェネレータ と 非同期関数 に関するBabelのドキュメントを参照してください。
それから、非同期プログラミングを容易にすることを目的とした、他の JSコンパイル言語 もあります。それらは通常await
、(例えば Iced CoffeeScript )に似た構文を使いますが、Haskellのようなdo
-表記を特徴とするものもあります(例えば LatteJs 、 モナディック 、 PureScript または LispyScript )。
チェーンの中間値にアクセスする必要がある場合は、チェーンを必要な部分に分割してください。 1つのコールバックをアタッチして、どういうわけかそのパラメータを複数回使用しようとする代わりに、同じ約束に複数のコールバックをアタッチします - あなたが結果の値を必要とするときはいつでも。忘れないでください、 の約束は未来の価値 を表しているだけです。線形チェーンで一方の約束を他方の約束から派生させることの次に、結果の値を構築するためにあなたのライブラリからあなたに与えられた約束コンビネータを使用してください。
これにより、非常に簡単な制御フロー、明確な機能の構成、そしてそれによる容易なモジュール化がもたらされる。
function getExample() {
var a = promiseA(…);
var b = a.then(function(resultA) {
// some processing
return promiseB(…);
});
return Promise.all([a, b]).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
ES6でのみ使用可能になったPromise.all
の後のコールバックでのパラメータのデストラクタリングの代わりに、ES5ではthen
呼び出しは多くのプロミスライブラリによって提供されていた気の利いたヘルパーメソッドに置き換えられます( Q )、 Bluebird 、 の場合 、…):.spread(function(resultA, resultB) { …
。
Bluebirdには、そのPromise.all
+ join
の組み合わせをより単純な(そしてより効率的な)構文に置き換えるための専用 spread
関数 もあります。
…
return Promise.join(a, b, function(resultA, resultB) { … });
後で必要な値の約束を変数に割り当ててから、同期検査を介してその値を取得します。この例ではbluebirdの.value()
メソッドを使用していますが、多くのライブラリが同様のメソッドを提供しています。
function getExample() {
var a = promiseA(…);
return a.then(function() {
// some processing
return promiseB(…);
}).then(function(resultB) {
// a is guaranteed to be fulfilled here so we can just retrieve its
// value synchronously
var aValue = a.value();
});
}
これは好きなだけ多くの値に使用することができます。
function getExample() {
var a = promiseA(…);
var b = a.then(function() {
return promiseB(…)
});
var c = b.then(function() {
return promiseC(…);
});
var d = c.then(function() {
return promiseD(…);
});
return d.then(function() {
return a.value() + b.value() + c.value() + d.value();
});
}
変数のスコープ(この場合、成功コールバック関数パラメーター)を維持するためにクロージャーを使用するのは、自然なJavaScriptソリューションです。 promiseを使用すると、任意に nest and flatten.then()
コールバックを実行できます-内側のスコープを除き、それらは意味的に同等です。
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
もちろん、これはインデントピラミッドを構築しています。インデントが大きくなりすぎている場合でも、古いツールを適用して Doom of Doom に対抗できます。モジュール化、追加の名前付き関数の使用、変数が不要になり次第プロミスチェーンのフラット化もう。
理論的には、(すべてのクロージャーを明示的にすることで)常に2レベル以上のネストを回避でき、実際には合理的な範囲で使用します。
function getExample() {
// preprocessing
return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
return function(resultA) {
// some processing
return promiseB(…).then(makeBhandler(resultA, …));
};
}
function makeBhandler(resultA, …) {
return function(resultB) {
// more processing
return // anything that uses the variables in scope
};
}
この種類のヘルパー関数を使用することもできます 部分的なアプリケーション 、たとえば_.partial
from nderscore / lodash または native .bind()
method 、インデントをさらに減らすため:
function getExample() {
// preprocessing
return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
// some processing
return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
// more processing
return // anything that uses resultA and resultB
}
コールバックをネストするのと同様に、この手法はクロージャに依存しています。それでも、チェーンはフラットのままです。最新の結果だけを渡すのではなく、ステートオブジェクトがすべてのステップで渡されます。これらの状態オブジェクトは、前のアクションの結果を蓄積し、後で必要になるすべての値と現在のタスクの結果を渡します。
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
ここでは、その小さな矢印b => [resultA, b]
がresultA
を閉じて両方の結果の配列を次のステップに渡す関数です。これはパラメータ分解構文を使用して、単一の変数に分割します。
破壊がES6で利用可能になる前に、.spread()
と呼ばれる気の利いたヘルパーメソッドが多くの約束のライブラリによって提供されました( Q 、 Bluebird 、 とき 、…)。これは、.spread(function(resultA, resultB) { …
として使用される複数のパラメータ(各配列要素に対して1つ)を持つ関数を取ります。
もちろん、ここで必要とされるクロージャーはいくつかのヘルパー関数によってさらに単純化することができます。
function addTo(x) {
// imagine complex `arguments` fiddling or anything that helps usability
// but you get the idea with this simple one:
return res => [x, res];
}
…
return promiseB(…).then(addTo(resultA));
あるいは、Promise.all
を使って配列の約束を生成することもできます。
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
// as if passed to Promise.resolve()
}).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
また、配列だけでなく、任意に複雑なオブジェクトを使用することもできます。たとえば、 _.extend
または Object.assign
を別のヘルパー関数で使用すると、次のようになります。
function augment(obj, name) {
return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(augment({resultA}, "resultB"));
}).then(function(obj) {
// more processing
return // something using both obj.resultA and obj.resultB
});
}
このパターンはフラットチェーンを保証し、明示的な状態オブジェクトは明確さを向上させることができますが、長いチェーンにとっては面倒になります。特にあなたが状態を散発的にしか必要としないとき、あなたはまだすべてのステップを通してそれを通過しなければなりません。この固定されたインタフェースでは、チェーン内の単一のコールバックはかなり密接に結合されており、変更に柔軟性がありません。それは単一のステップを取り除くことをより困難にします、そしてコールバックは他のモジュールから直接供給されることができません - それらは常に状態を気にする定型コードで包まれる必要があります。上記のような抽象ヘルパー関数は痛みを少し軽減することができますが、常に存在します。
些細な(しかし巧妙ではなくエラーが発生しやすい)解決策は、(チェーン内のすべてのコールバックがアクセス可能な)よりスコープの広い変数を使用し、取得時に結果値を書き込むことです。
function getExample() {
var resultA;
return promiseA(…).then(function(_resultA) {
resultA = _resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both resultA and resultB
});
}
多くの変数の代わりに、(最初は空の)オブジェクトを使用することもできます。このオブジェクトには、結果が動的に作成されたプロパティとして格納されます。
この解決策にはいくつかの欠点があります。
Bluebirdライブラリは、 それらのbind()
メソッド を使用して、約束されたチェーンにコンテキストオブジェクトを割り当てることで、一緒に渡されるオブジェクトの使用を推奨します。それはそうでなければ使用できない this
キーワード を介して各コールバック関数からアクセス可能になります。オブジェクトのプロパティは変数よりも検出されないタイプミスが発生しやすいのですが、パターンはかなり巧妙です。
function getExample() {
return promiseA(…)
.bind({}) // Bluebird only!
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}).bind(); // don't forget to unbind the object if you don't want the
// caller to access it
}
このアプローチは、.bindをサポートしないpromiseライブラリで簡単にシミュレートできます(ただし多少冗長な方法であり、式では使用できません)。
function getExample() {
var ctx = {};
return promiseA(…)
.then(function(resultA) {
this.resultA = resultA;
// some processing
return promiseB(…);
}.bind(ctx)).then(function(resultB) {
// more processing
return // something using both this.resultA and resultB
}.bind(ctx));
}
ローカルスコープのオブジェクトを使用して中間結果をプロミスチェーンに集めることは、あなたが提起した質問に対する合理的なアプローチです。次のコードを見てください。
function getExample(){
//locally scoped
const results = {};
return promiseA(...).then(function(resultA){
results.a = resultA;
return promiseB(...);
}).then(function(resultB){
results.b = resultB;
return promiseC(...);
}).then(function(resultC){
//Resolve with composite of all promises
return Promise.resolve(results.a + results.b + resultC);
}).catch(function(error){
return Promise.reject(error);
});
}
ノード7.4は、ハーモニーフラグ付きの非同期/待機呼び出しをサポートします。
これを試して:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
ファイルを次のように実行します。
node --harmony-async-await getExample.js
することができますようにシンプル!
このごろ、私はあなたのようないくつかの質問にも出会っています。ついに、私は質問に良い解決策を見つける、それは読むのが簡単で良いです。これがお役に立てば幸いです。
how-to-chain-javascript-promisesに従って
それでは、コードを見てみましょう。
const firstPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('first promise is completed');
resolve({data: '123'});
}, 2000);
});
};
const secondPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('second promise is completed');
resolve({newData: `${someStuff.data} some more data`});
}, 2000);
});
};
const thirdPromise = (someStuff) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('third promise is completed');
resolve({result: someStuff});
}, 2000);
});
};
firstPromise()
.then(secondPromise)
.then(thirdPromise)
.then(data => {
console.log(data);
});
babel-node
version <6を使ったもう一つの答え
async - await
を使う
npm install -g [email protected]
example.js:
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
それから、babel-node example.js
を実行してください。
別の答えは、シーケンシャルエグゼキュータ nsynjs を使用することです。
function getExample(){
var response1 = returnPromise1().data;
// promise1 is resolved at this point, '.data' has the result from resolve(result)
var response2 = returnPromise2().data;
// promise2 is resolved at this point, '.data' has the result from resolve(result)
console.log(response, response2);
}
nynjs.run(getExample,{},function(){
console.log('all done');
})
function synchronousCode() {
var urls=[
"https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
];
for(var i=0; i<urls.length; i++) {
var len=window.fetch(urls[i]).data.text().data.length;
// ^ ^
// | +- 2-nd promise result
// | assigned to 'data'
// |
// +-- 1-st promise result assigned to 'data'
//
console.log('URL #'+i+' : '+urls[i]+", length: "+len);
}
}
nsynjs.run(synchronousCode,{},function(){
console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Bluebirdを使うときは、.bind
メソッドを使ってプロミスチェーンで変数を共有することができます。
somethingAsync().bind({})
.spread(function (aValue, bValue) {
this.aValue = aValue;
this.bValue = bValue;
return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
return this.aValue + this.bValue + cValue;
});
詳細については、このリンクを確認してください。
function getExample() {
var retA, retB;
return promiseA(…).then(function(resultA) {
retA = resultA;
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
//retA is value of promiseA
return // How do I gain access to resultA here?
});
}
簡単な方法:D
グローバル変数を使用することは大好きではないので、このパターンを自分のコードで使用するつもりはありません。しかし、ピンチでそれは動作します。
ユーザーは、公認のマングースモデルです。
var globalVar = '';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
RSVPのハッシュが使えると思います。
以下のようなもの:
const mainPromise = () => {
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('first promise is completed');
resolve({data: '123'});
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('second promise is completed');
resolve({data: '456'});
}, 2000);
});
return new RSVP.hash({
prom1: promise1,
prom2: promise2
});
};
mainPromise()
.then(data => {
console.log(data.prom1);
console.log(data.prom2);
});
溶液:
「bind」を使用して、後の「then」関数のスコープに中間値を明示的に配置できます。これは、Promiseの動作を変更する必要がなく、エラーが既に伝播されているように値を伝播するのに1行または2行のコードのみを必要とするニースのソリューションです。
完全な例を次に示します。
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
} // pLogInfo
// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final 'then' in the chain: Writes "local info, server info"
return pWriteFile('log',this.localInfo+','+serverInfo);
} // p2
このソリューションは、次のように呼び出すことができます。
pLogInfo("local info").then().catch(err);
(注:このソリューションのより複雑で完全なバージョンがテストされていますが、このサンプルバージョンではないため、バグがある可能性があります。)