web-dev-qa-db-ja.com

プロミスから複数の値を適切に返すにはどうすればよいですか?

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

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

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

ここで、amazingData内のafterSomethingElseにアクセスしたい状況が発生する可能性があります。

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

Q.spreadがあるので、この可能性について疑問に思っているだけです。

67
Der Hochstapler

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

約束は本質的に単一の値で解決します-これはQの仕組み、 Promises/A +仕様の仕組み および 抽象化 の仕組みの一部です。

最も近いのは、Q.spreadを使用して配列を返すか、ES6がサポートされている場合、またはBabelJSのようなトランスピレーションツールを使用する場合はES6の構造化を使用することです。

コンテキストをプロミスチェーンに渡す方法については、 Bergiの優れた標準 を参照してください。

71

渡すことができる値は1つだけですが、たとえば次のように、複数の値を含む配列にすることができます。

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

反対側では、ES2015のdestructuring式を使用して個々の値を取得できます。

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"
})
24
Alejandro Silva

両方の値を含むオブジェクトを返すことができます-それに問題はありません。

別の戦略は、値を渡すのではなく、クロージャを介してkeepにすることです。

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
  });
}
18
Kevin Reid

あなたができる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
}
5
jemiloii

ここにあなたがすべきであると思う方法があります。

チェーンを分割する

両方の関数がamazingDataを使用するため、専用の関数で使用することは理にかなっています。私は通常、データを再利用するたびにそれを行います。したがって、データは常に関数argとして存在します。

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

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

また、通常はPromiseを使用する方法であるため、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()の場合と同じです。非同期でも非同期でもかまいません。約束が返されるか、解決された約束に値がラップされます。


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

また、関数の名前をthenに短くしておくのも好きです。とにかくローカルでのみ定義されます。そして、ほとんどの場合、彼らは別の場所で定義された別の関数を呼び出します-再利用可能-仕事をします。パラメータが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()に集中しすぎないでください。これは、解決された約束を作成するための簡単な方法です。私がこれを達成しようとしているのは、実行しているすべてのコードを単一の場所、つまりthensのすぐ下に置くことです。よりわかりやすい名前を持つ他のすべての関数は再利用可能です。

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


他の回答で定義されている配列またはオブジェクトを使用しても機能します。ある意味これは ケビンリードによって提案された答え です。

bind()またはPromise.all()も使用できます。ただし、コードを分割する必要があります。

バインドを使用して

関数を再利用可能に保ちたいが、thenの中にあるものを非常に短く保つ必要がない場合は、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()にデータを渡すことができます-配列の存在に注意してください-約束がある限り、約束が失敗しないことを確認してください。 。

そして、args引数から新しい変数を定義する代わりに、代わりにspread()を使用できるはずです。then()あらゆる種類の素晴らしい仕事。

2
gabriel

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

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
user6507599

Promiseから返されるものはすべて、次の.then()ステージでラップ解除されるpromiseにラップされます。

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

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

これらの場合、次のようなPromise.all()ステージでアンラップされたp1およびp2の約束を取得するために.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 */);
1
Redu

ObservableRxjs で表され、複数値を返すことができます。

1
codelovesme