.then()チェーンで以前のpromise結果にアクセスするにはどうすればよいですか?


650

私はに私のコードを再構築してきた約束、そして素晴らしい長い建てフラット約束チェーンを複数からなる、.then()コールバック。最後に、いくつかの複合値を返し、複数の中間promise結果にアクセスする必要があります。ただし、シーケンスの中央からの解決値は、最後のコールバックのスコープ内にありません。それらにアクセスするにはどうすればよいですか?

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?
    });
}

2
この質問は非常に興味深いものでありjavascript、タグが付けられていても、他の言語に関連しています。私はJavaとjdeferred「チェーンを壊す」という答えを使用しています
gontard

回答:


377

鎖を断ち切る

チェーンの中間値にアクセスする必要がある場合は、チェーンを必要な単一のピースに分割する必要があります。1つのコールバックをアタッチし、どういうわけかそのパラメーターを複数回使用しようとする代わりに、複数のコールバックを同じプロミス(結果の値が必要な場所)にアタッチします。忘れないでください。約束は未来の価値を表すだけです(プロキシ)。線形チェーンで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
    });
}

Promise.allES6でのみ使用可能になった後のコールバックでのパラメーターのデストラチャリングの代わりに、ES5では、then呼び出しは多くのpromiseライブラリ(QBluebirdいつ、…)によって提供される気の利いたヘルパーメソッドに置き換えられます.spread(function(resultA, resultB) { …

ブルーバードは、専用の特長join機能をその置き換えるためにPromise.all+のspread単純(かつ効率的)構造と組み合わせました:


return Promise.join(a, b, function(resultA, resultB) {  });

1
配列内の関数は順番に実行されていますか?
scaryguy 2015

6
@scaryguy:配列には関数はありません。これらは約束です。promiseAそして、promiseB(約束は-を返す)関数はここにあります。
Bergi

2
@Rolandはそれを次のように述べたことはありません:-)この回答はES5時代に書かれたもので、標準にはまったく約束がなくspread、このパターンでは非常に役に立ちました。より最新のソリューションについては、承認された回答を参照してください。ただし、明示的なパススルーの回答は既に更新しており、これも更新しない理由はありません。
Bergi

1
@reifyいいえ、それを行うべきではありません。拒否すると問題が発生します。
Bergi、

1
この例は理解できません。値がチェーン全体に伝搬されることを要求する 'then'ステートメントのチェーンがある場合、これがどのように問題を解決するのかわかりません。以前の値を必要とするPromiseは、その値が存在するまで発動(作成)できません。さらに、Promise.all()は、リスト内のすべてのpromiseが終了するのを待つだけです。注文を課すことはありません。したがって、各「次の」関数が以前のすべての値にアクセスできるようにする必要があり、あなたの例がどのようにそれを行うかわかりません。私はそれを信じたり理解したりしていないので、あなたはあなたの例を私たちに説明するべきです。
David Spector

238

ECMAScriptハーモニー

もちろん、この問題は言語設計者にも認識されています。彼らは多くの仕事をし、非同期機能の提案はついにそれを作りました

ECMAScript 8

then非同期関数(呼び出されたときにプロミスを返す)のように、プロミスが直接解決されるのを待つだけでよいので、単一の呼び出しやコールバック関数はもう必要ありません。また、条件、ループ、try-catch-clausesなどの任意の制御構造も備えていますが、便宜上ここでは必要ありません。

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

ES8を待っている間、すでに非常によく似た種類の構文を使用していました。ES6にはジェネレータ関数が付属しており、任意に配置されたyieldキーワードで実行をバラバラに分解できます。これらのスライスは、独立して、非同期でも、相互に実行できます。これは、次のステップを実行する前にpromiseの解決を待機したい場合に実行します。

専用ライブラリ(cotask.jsなど)がありますが、多くのpromiseライブラリにはヘルパー関数(QBluebirdwhen、…)があり、ジェネレーター関数を指定すると、この非同期の段階的な実行が行われます約束をもたらす。

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で機能しました。また、いくつかのブラウザー(またはそれらの開発版)は、比較的早期にジェネレーター構文をサポートしていました。

ECMAScript 5

ただし、下位互換性を維持する必要がある場合は、トランスパイラなしでは使用できません。ジェネレーター関数と非同期関数の両方が現在のツールでサポートされています。たとえば、ジェネレーター非同期関数に関するBabelのドキュメントを参照してください。

そして、 非同期プログラミングの緩和に特化した他の多くのcompile-to-JS言語もあります。通常はに似た構文を使用しますawait(例:Iced CoffeeScript)。ただし、Haskellのような表記法を使用するものもありますdo(例:LatteJsモナディックPureScriptまたはLispyScript)。


@Bergi外部コードからの非同期関数の例getExample()を待つ必要がありますか?
arisalexis 2015

@arisalexis:はい、getExampleまだプロミスを返す関数です。他の回答の関数と同じように機能しますが、構文が優れています。await別のasync関数を呼び出すことも、.then()その結果にチェーンすることもできます。
Bergi

1
気になったのですが、なぜ質問した直後に自分の質問に答えたのですか。ここで良い議論がありますが、私は興味があります。たぶん、質問した後で自分で答えを見つけましたか?
granmoe 2016年

@granmoe:正規の複製ターゲットとして意図的に全体の議論を投稿しました
Bergi

ジェネレーター関数を使用したECMAScript 6の例でPromise.coroutine(つまり、Bluebirdや他のライブラリを使用せず、単純なJSのみを使用)を使用しないようにする(面倒ではない)方法はありますか?私は何かのようなことを念頭に置いていましたsteps.next().value.then(steps.next)...が、それはうまくいきませんでした。
学習者に名前がない

102

同期検査

後で必要になる値を変数に割り当て、同期検査で値を取得します。例では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();
    });
}

6
これは私のお気に入りの答えです。ライブラリや言語機能への依存性を最小限に抑え、読みやすく、拡張可能です
Jason

13
@ジェイソン:ええと、「ライブラリ機能への最小限の依存」?同期検査はライブラリ機能であり、起動するのはまったく非標準的な機能です。
Bergi、

2
彼はライブラリ固有の機能を意味していたと思います
デスゲイズ2017

54

ネスト(および)クロージャー

変数のスコープ(この場合は、成功コールバック関数のパラメーター)を維持するためにクロージャーを使用することは、自然なJavaScriptソリューションです。promiseを使用すると、 .then()コールバックを任意にネストおよびフラット化できます。これらは、内部のスコープを除いて、意味的に同等です。

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

もちろん、これはインデントピラミッドを構築しています。インデントが大きくなりすぎても、古いツールを適用して運命ピラミッドに対抗することができます。モジュール化し、追加の名前付き関数を使用し、変数が不要になったらすぐにプロミスチェーンをフラット化します。
理論的には、(すべてのクロージャーを明示的にすることにより)常に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
    };
}

また、アンダースコア / lodashネイティブメソッドなど、この種の部分的なアプリケーションにヘルパー関数を使用して、インデントをさらに減らすこともできます_.partial.bind()

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
}

5
これと同じ提案が、約束pouchdb.com/2015/05/18/we-have-a-problem-with-promises.htmlに関するNolan Lawsonの記事の「高度な間違い#4」の解決策として提供されています。それは良い読み物です。
Robert

2
これはまさにbindモナドの関数です。Haskellは、非同期/待機構文のように見えるようにする構文糖(do表記)を提供します。
zeronone 2016

50

明示的なパススルー

コールバックのネストと同様に、この手法はクロージャに依存しています。ただし、チェーンはフラットのままです。最新の結果のみを渡すのではなく、一部の状態オブジェクトがすべてのステップで渡されます。これらの状態オブジェクトは、前のアクションの結果を蓄積し、後で再び必要になるすべての値と現在のタスクの結果を伝えます。

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()は、多くのpromiseライブラリ(QBluebird when、…)。これは、複数のパラメーター(配列要素ごとに1つ)を持つ関数を使用して、.spread(function(resultA, resultB) { …ます。

もちろん、ここで必要なクロージャーは、いくつかのヘルパー関数によってさらに簡略化できます。

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配列のpromiseを生成するために採用できます。

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
    });
}

このパターンはフラットチェーンを保証し、明示的な状態オブジェクトは明確性を向上させることができますが、長いチェーンでは面倒になります。特に、散発的にしか状態を必要としない場合は、すべてのステップを通過する必要があります。この固定されたインターフェースにより、チェーン内の単一のコールバックはかなり密に結合され、変更に柔軟性がなくなります。これにより、シングルステップの因数分解が困難になり、コールバックを他のモジュールから直接提供することはできません。これらは常に、状態を気にするボイラープレートコードでラップする必要があります。上記のような抽象的なヘルパー関数は少し苦痛を軽減できますが、常に存在します。


まず、私はを省略した構文Promise.allは推奨されないと思います(ES6では、構造化によって置き換えられ、a .spreadをaに切り替えると、then予期しない結果になることがよくあるため、機能しません。増強の時点で-なぜ必要かわからないaugmentを使用する-何かをプロトタイププロトタイプに追加することは、(現在サポートされていない)サブクラス化で拡張されるはずのES6プロミスを拡張するための受け入れられる方法ではありません
Benjamin Gruenbaum

@BenjaminGruenbaum:「構文省略Promise.all」とはどういう意味ですか?この回答のいずれの方法も、ES6では機能しません。a spreadからdestructuralへの切り替えでthenも問題は発生しません。.prototype.augmentについて:誰かがそれに気づくのはわかっていました。私は可能性を探りたいだけでした-編集します。
ベルギ

配列構文とは return [x,y]; }).spread(...はなく、return Promise.all([x, y]); }).spread(...どのES6非構造糖のためのスプレッドを交換しても約束は他のすべてとは異なる配列を返す扱う奇妙なエッジケースではないだろうというときは変更しないでしょう。
Benjamin Gruenbaum、2015年

3
これがおそらく最良の答えです。約束は「関数型リアクティブプログラミング」ライトです。これは多くの場合、採用されているソリューションです。たとえば、BaconJsには#combineTemplateがあり、結果をチェーンに渡されるオブジェクトに組み合わせることができます
U Avalos

1
@CapiEtherielその答えは、ES6が今日ほど広く普及していなかったときに書かれました。ええ、例を交換する時がきたかもしれません
Bergi

35

変更可能なコンテキスト状態

ささいな(しかし、エレガントでなくエラーが発生しやすい)ソリューションは、より高いスコープの変数(チェーン内のすべてのコールバックがアクセスできる)を使用し、それらを取得したときに結果値を書き込むことです。

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
    });
}

多くの変数の代わりに、(最初は空の)オブジェクトを使用することもできます。このオブジェクトには、結果が動的に作成されたプロパティとして格納されます。

このソリューションにはいくつかの欠点があります。

  • 可変状態は醜くグローバル変数は悪です。
  • このパターンは関数の境界を越えて機能しません。宣言が共有スコープを離れてはならないため、関数のモジュール化はより困難です。
  • 変数のスコープは、初期化される前に変数にアクセスすることを妨げません。これは、競合状態が発生する可能性のある複雑なpromise構造(ループ、分岐、例外)で特に発生しやすくなります。状態を明示的に渡すと、励ましを約束する宣言型の設計により、これを防ぐことができるよりクリーンなコーディングスタイルが強制されます。
  • これらのシェア変数のスコープを正しく選択する必要があります。たとえば、状態がインスタンスに格納されている場合のように、複数の並列呼び出し間の競合状態を防ぐために、実行される関数に対してローカルである必要があります。

Bluebirdライブラリは、渡されたオブジェクトの使用を奨励し、そのbind()メソッドを使用して、コンテキストオブジェクトをpromiseチェーンに割り当てます。それ以外の場合は使用できない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));
}

.bind()メモリリークを防ぐために必要ではありません
エサイリヤ2015年

@Esailija:しかし、返されたpromiseは、コンテキストオブジェクトへの参照を保持しませんか?もちろん、ガベージコレクションは後で処理します。約束が決して処分されない限り、それは「漏洩」ではありません。
Bergi、2015年

はい。ただし、約束はその履行値とエラー理由への参照も保持します...しかし、約束への参照は何も保持しないため、問題になりません
エサイリア2015年

4
プリアンブルにほとんど投票したので、この回答を2つに分けてください。「平凡な(しかし、洗練されておらず、エラーが発生しやすい)ソリューション」は、最もクリーンでシンプルなソリューションだと思います。閉鎖はグローバルでも悪でもありません。このアプローチに対して与えられた議論は、前提を与えられた私には意味がありません。「素晴らしい長いフラットプロミスチェーン」を与えると、どのようなモジュール化の問題が発生しますか?
ジブ2015

2
上で述べたように、Promiseは「関数型リアクティブプログラミング」ライトです。これはFRPのアンチパターンです
U Avalos

15

「Mutable contextual state」のそれほど厳しくないスピン

ローカルスコープのオブジェクトを使用して、Promiseチェーンの中間結果を収集することは、あなたが提起した質問への合理的なアプローチです。次のスニペットを考えてみましょう:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • グローバル変数は悪いので、このソリューションは害を引き起こさないローカルスコープの変数を使用します。関数内でのみアクセスできます。
  • 変更可能な状態は醜いですが、これは醜い方法で状態を変更しません。醜い可変状態は伝統的に関数の引数またはグローバル変数の状態を変更することを指しますが、このアプローチは単に、promiseの結果を集約することのみを目的として存在するローカルスコープの変数の状態を変更するだけです...約束が解決したら。
  • 中間プロミスは結果オブジェクトの状態へのアクセスを妨げられませんが、これはチェーン内のプロミスの1つが不正になり、結果を妨害するという恐ろしいシナリオをもたらしません。promiseの各ステップで値を設定する責任はこの関数に限定され、全​​体的な結果は正しいか正しくないかのいずれかです...数年後に本番環境で発生するバグではありません(意図しない限り) !)
  • getExample関数の呼び出しごとに結果変数の新しいインスタンスが作成されるため、これは並列呼び出しから生じる競合状態のシナリオを導入しません。

1
少なくともPromiseコンストラクタのアンチパターンは避けてください!
Bergi

@Bergiに感謝します。あなたがそれを言うまで、私はそれがアンチパターンであることにさえ気がつきませんでした!
ジェイ

これは、promise関連のエラーを軽減するための適切な回避策です。ES5を使用していて、promiseで動作する別のライブラリを追加したくありませんでした。
nilakantha singh deo 2018

8

ノード7.4は、ハーモニーフラグを使用した非同期/待機呼び出しをサポートするようになりました。

これを試して:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

そして、ファイルを実行します:

node --harmony-async-await getExample.js

できる限りシンプル!


8

この頃、私もあなたのようないくつかの質問をします。最後に、私はその質問で良い解決策を見つけました。それはシンプルで読みやすいです。これがお役に立てば幸いです。

よるとどのようにツーチェーンのjavascript-約束

さて、コードを見てみましょう:

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);
    });

4
これは、チェーン内の以前の結果にアクセスする方法に関する質問には実際には答えません。
Bergi

2
すべての約束は以前の価値を得ることができます、あなたの意味は何ですか?
yzfdjzwl 2017

1
質問のコードを見てください。その目的は、要求された約束の結果を得ることではなく、その前の結果を得る.thenことです。たとえばthirdPromise、の結果にアクセスしfirstPromiseます。
Bergi

6

babel-nodeバージョン6 を使用した別の回答

使用する async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

次に、実行babel-node example.jsして出来上がり!


1
はい、投稿しました。それでも、いつかES7が利用可能になると言うのではなく、実際にES7を使用して立ち上げて実行する方法を説明しているので、このままにしておきます。
Anthony

1
そうそう、私は私の答えを更新して、これらの「実験的」プラグインがすでにここにあると言うべきです。
Bergi、2015年

2

私は自分のコードでこのパターンを使用するつもりはありません。私はグローバル変数を使用するのがあまり好きではないからです。ただし、ピンチでは機能します。

ユーザーは有望なマングースモデルです。

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

2
このパターンは、Mutableのコンテキスト状態の回答ですでに詳しく説明されていることに注意してください(また、なぜ醜いのか-私も大ファンではありません)
Bergi

あなたの場合、パターンは役に立たないようです。あなたはまったく必要ありませんglobalVar、ただ必要User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });ですか?
ベルギ

1
私自身のコードでは個人的には必要ありませんが、ユーザーは2番目の関数で非同期を実行してから、元のPromise呼び出しとやり取りする必要がある場合があります。しかし、前述のように、この場合はジェネレータを使用します。:)
アンソニー

2

シーケンシャルエグゼキューター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>


1

bluebirdを使用する場合、.bindメソッドを使用してpromiseチェーンで変数を共有できます。

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;
});

詳細については、このリンクを確認してください:

http://bluebirdjs.com/docs/api/promise.bind.html


このパターンは、Mutable contextual state answer
Bergi

1
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


この答えに気づきましたか?
Bergi

1

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);
    });

はい、これPromise.allソリューションと同じですが、配列ではなくオブジェクトのみが含まれます。
ベルギ

0

解決:

'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);

(注:このソリューションのより複雑で完全なバージョンがテストされていますが、このサンプルバージョンはテストされていないため、バグが発生する可能性があります。)


これは、入れ子(および)クロージャの回答同じパターンのようです
ベルギ

それは似ています。新しいAsync / Await構文には引数の自動バインディングが含まれているため、すべての引数をすべての非同期関数で使用できることがわかりました。私は約束を放棄しています。
David Spector

async/ awaitは、promiseを使用することを意味します。放棄する可能性があるのthenは、コールバックを伴う呼び出しです。
ベルギ

-1

私がプロミスについて知っていることは、可能であればそれらを参照しないように戻り値としてのみそれを使用することです。そのためには、非同期/待機構文が特に実用的です。今日、すべての最新のブラウザーとノードがそれをサポートしています:https : //caniuse.com/#feat=async-functionsは単純な動作で、コードは同期コードを読み取るようなもので、コールバックを忘れます...

promiseを参照する必要がある場合は、作成/解決が独立した/関連のない場所で行われる場合です。したがって、代わりに人工的な関連付けと、おそらく「遠い」プロミスを解決するためのイベントリスナーでは、次のコードが有効なes5で実装するDeferredとしてプロミスを公開することを好みます。

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

私のtypescriptプロジェクトを翻訳しました:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

より複雑なケースでは、依存関係をテストして入力せずに、これらの小さなプロミスユーティリティをよく使用します。p-mapは何度か役に立ちました。私は彼がほとんどのユースケースをカバーしたと思います:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=


変更可能なコンテキスト状態または同期検査のいずれかを提案しているように聞こえますか?
ベルギ

@bergi初めて私はそれらの名前を率いました。リストに追加していただきありがとうございます。この種の自己認識の約束はDeferredの名前でわかっています。約束の作成と解決の責任が独立しているので、約束を解決するためだけにそれらを関連付ける必要がない場合、私はこのパターンをしばしば必要とします。私はあなたの例ではなく、クラスを使用して適応しましたが、おそらく同等です。
Cancerbero
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.