約束から複数の値を適切に返すにはどうすればよいですか?


86

最近、特定の状況に何度か遭遇しましたが、適切に解決する方法がわかりませんでした。次のコードを想定します。

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

私がアクセスしたいでしょう今どこ状況が生じる可能性があるamazingDataではafterSomethingElse

明らかな解決策の1つはafterSomething、から配列またはハッシュを返すことです。これは、関数から返すことができる値は1つだけだからです。しかしafterSomethingElse、2つのパラメーターを受け入れて同様に呼び出す方法があるかどうか疑問に思っています。これは、文書化して理解するのがはるかに簡単なようです。

私がQ.spread望んでいるのと同じようなことをするがあるので、私はこの可能性についてだけ疑問に思っています。




ES6で割り当てを構造化解除すると役立ちます。ここを
Ravi Teja

回答:


88

関数から複数の値を返すことができないのと同じように、複数のプロパティを持つPromiseを解決することはできません。promiseは概念的に時間の経過に伴う値を表すため、複合値を表すことはできますが、promiseに複数の値を入れることはできません。

promiseは本質的に単一の値で解決されます。これは、Qの動作、Promises / A +仕様の動作、および抽象化の動作の一部です。

取得できる最も近い方法はQ.spread、配列を使用して返すか、サポートされている場合、またはBabelJSなどの変換ツールを使用する場合はES6デストラクチャリングを使用することです。

コンテキストをプロミスチェーンに渡すことについては、Bergiの優れたカノニカルを参照してください。


16
複数のプロパティを持つオブジェクトで解決することの何が問題になっていますか?解決から複数の値を取得する簡単な方法のようです。
jfriend00 2015

6
それを行うのはまったく問題ありません
Benjamin Gruenbaum 2015

また、Promiseを拡張して.spread()、Bluebirdのように次の関連する回答に表示することもできます:stackoverflow.com/a/22776850/1624862
Kevin Ghadyani 2016年

Promise.all()の動作はこれと矛盾しているようです。 Promise.all([a, b, c]).then(function(x, y, z) {...})x、y、zがa、b、cの解決された値に評価される、すべての最新のJavascriptエンジンで正しく機能します。したがって、言語ではユーザーコードから簡単に(またはまともな)それを行うことができないと言う方が正確です(then句から直接Promiseを返すことができるため、値をpromiseでラップしてから、Promiseでラップすることができます) .all()は、複雑な方法ではありますが、目的の動作を取得します)。
オースティンヘメルガーン

3
ちょうど偽です@AustinHemmelgarn、Promise.all満たし配列を持ちます。でPromise.all([a,b]).then((a, b) => bですundefined。だからこそ.then(([a, b]) =>、破壊的な任務を行う必要があります
BenjaminGruenbaum19年

38

渡すことができる値は1つだけですが、例として、内部に複数の値を持つ配列にすることができます。

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

一方、ES2015の破壊式を使用して、個々の値を取得できます。

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

両方の約束を呼び出し、それらを連鎖させます。

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

5
それは呼ばれています非構造ではなく、「デコンストラクタ」、そしてそれはオペレータではありません: - /
Bergi

3
ところで、パラメータで破壊権を使用できます。-そうすればfunction step2([server, data]) { …、暗黙のグローバルに割り当てることも回避できます。また、例ではコンストラクターではなく、returnまたはを実際に使用する必要Promise.resolveがありnew Promiseます。
ベルギ2015

@Bergiの推薦に感謝します!
アレハンドロシルバ

19

両方の値を含むオブジェクトを返すことができます—それは何も悪いことではありません。

もう1つの戦略は、値を渡すのではなく、クロージャを介して値を保持することです。

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

部分的にインライン化された形式ではなく完全に(同等で、おそらくより一貫性があります):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

3
then別の内部に戻っても大丈夫thenですか?アンチパターンではありませんか?
robe0 0718

@ Robe007が言ったように、これは「コールバック地獄」に似ているのではないでしょうか?ここでは、コールバック関数の代わりにネストがブロックされます。これにより、約束をするという目的
そのものが無効になります

5

あなたができる2つのこと、オブジェクトを返す

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

スコープを使用してください!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

3

これがあなたがすべきだと私が考える方法です。

チェーンを分割する

どちらの関数もamazingDataを使用するため、専用の関数に含めるのは理にかなっています。私は通常、一部のデータを再利用するたびにこれを行うため、関数argとして常に存在します。

あなたの例はいくつかのコードを実行しているので、それはすべて関数内で宣言されていると思います。これをtoto()と呼びます。次に、afterSomething()afterSomethingElse()の両方を実行する別の関数があります。

function toto() {
    return somethingAsync()
        .then( tata );
}

また、通常はPromisesを使用する方法であるため、returnステートメントを追加したことに気付くでしょう。必要に応じてチェーンを継続できるように、常にPromiseを返します。ここで、somethingAsync()amazingDataを生成し、新しい関数内のどこでも利用できるようになります。

この新しい関数が通常どのように見えるかは、processAsync()も非同期ですか?

processAsyncは非同期ではありません

processAsync()が非同期でない場合、物事を過度に複雑にする理由はありません。いくつかの古い良いシーケンシャルコードがそれを作ります。

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

afterSomethingElse()が何か非同期を行っているかどうかは問題ではないことに注意してください。もしそうなら、約束が返され、チェーンは継続することができます。そうでない場合は、結果値が返されます。ただし、関数はthen()から呼び出されるため、値はとにかく(少なくとも生のJavascriptでは)promiseにラップされます。

processAsync非同期

場合processAsync()は非同期で、コードが若干異なって見えます。ここでは、afterSomething()afterSomethingElse()が他の場所で再利用されないことを考慮します。

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

afterSomethingElse()については前と同じです。非同期でもそうでなくてもかまいません。promiseが返されるか、解決されたpromiseに値がラップされます。


あなたのコーディングスタイルは私が以前行っていたものに非常に近いので、2年経っても答えました。私はどこにでも匿名関数を持つことの大ファンではありません。読みづらいと思います。たとえそれがコミュニティでかなり一般的であるとしても。これは、callback-hellpromise-purgatoryに置き換えたときのことです。

私はまた、内の関数の名前を維持したい、その後短いです。とにかく、それらはローカルでのみ定義されます。そして、ほとんどの場合、他の場所で定義されている別の関数(再利用可能)を呼び出して、ジョブを実行します。パラメータが1つしかない関数に対してもこれを行うので、関数シグネチャにパラメータを追加/削除するときに関数を出し入れする必要はありません。

食事の例

次に例を示します。

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

Promise.resolve()に集中しすぎないでください。これは、解決されたプロミスを作成するための簡単な方法です。これによって私が達成しようとしているのは、実行しているすべてのコードを1つの場所(thensのすぐ下)に配置することです。よりわかりやすい名前を持つ他のすべての関数は再利用可能です。

この手法の欠点は、多くの関数を定義していることです。しかし、あちこちで匿名関数を持つことを避けるために、私が恐れているのは必要な苦痛です。そしてとにかくリスクは何ですか:スタックオーバーフロー?(冗談で!)


他の回答で定義されている配列またはオブジェクトを使用することもできます。これはある意味でKevinReidによって提案され答えです。

bind()またはPromise.all()を使用することもできます。それでもコードを分割する必要があることに注意してください。

バインドを使用する

関数を再利用可能に保ちたいが、その内部にあるもの非常に短く保つ必要がない場合は、bind()を使用できます。

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

簡単にするために、bind()は、呼び出されたときに、引数のリスト(最初のリストを除く)を関数の前に追加します。

Promise.allを使用する

あなたの投稿では、spread()の使用について言及しました。私はあなたが使用しているフレームワークを使用したことはありませんが、これがあなたがそれを使用できるはずの方法です。

Promise.all()がすべての問題の解決策であると信じている人もいるので、言及する価値があると思います。

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

Promise.all()にデータを渡すことができます配列の存在に注意してください)がpromiseが失敗しないことを確認してください。失敗しないと、処理が停止します。

そして、args引数から新しい変数を定義する代わりに、then()の代わりにspread()を使用して、あらゆる種類のすばらしい作業を行うことができるはずです。


3

オブジェクトを作成し、そのオブジェクトから引数を抽出するだけです。

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

promiseResolutionから引数を引き出します。

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

2

約束から戻ったものはすべて、次の.then()段階で解かれる約束に包まれます。

次のような1つ以上の同期値と一緒に1つ以上のpromiseを返す必要がある場合に興味深いものになります。

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

これらのケースでは、使用することが不可欠となりPromise.all()得るために、p1p2約束は、次のラップ解除.then()などのステージ

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);


0

タプルを返すだけです。

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.