Wie können Sie mehrere Werte aus einem Versprechen richtig zurückgeben?

85

Ich bin kürzlich ein paar Mal in eine bestimmte Situation geraten, die ich nicht richtig lösen konnte. Nehmen Sie den folgenden Code an:

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

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

Jetzt könnte eine Situation entstehen, in der ich Zugang zu amazingDatain haben möchte afterSomethingElse.

Eine naheliegende Lösung wäre, ein Array oder einen Hash von zurückzugeben afterSomething, da Sie nur einen Wert von einer Funktion zurückgeben können. Aber ich frage mich, ob es eine Möglichkeit gibt afterSomethingElse, zwei Parameter zu akzeptieren und sie ebenfalls aufzurufen, da dies viel einfacher zu dokumentieren und zu verstehen scheint.

Ich wundere mich nur über diese Möglichkeit, da es Q.spreadetwas gibt , das etwas Ähnliches tut, was ich will.

Der Hochstapler
quelle

Antworten:

88

Sie können ein Versprechen nicht mit mehreren Eigenschaften auflösen, genauso wie Sie nicht mehrere Werte von einer Funktion zurückgeben können . Ein Versprechen stellt konzeptionell einen Wert im Laufe der Zeit dar. Während Sie also zusammengesetzte Werte darstellen können, können Sie nicht mehrere Werte in ein Versprechen einfügen.

Ein Versprechen wird von Natur aus mit einem einzigen Wert aufgelöst. Dies ist Teil der Funktionsweise von Q, der Funktionsweise der Versprechen / A + -Spezifikation und der Funktionsweise der Abstraktion .

Das Beste, was Sie bekommen können, ist die Verwendung Q.spreadund Rückgabe von Arrays oder die Verwendung der ES6-Destrukturierung, wenn dies unterstützt wird oder Sie bereit sind, ein Transpilationswerkzeug wie BabelJS zu verwenden.

Bezüglich der Weitergabe des Kontexts an eine Versprechenskette verweisen wir bitte auf Bergis exzellenten Kanon .

Benjamin Gruenbaum
quelle
15
Was ist falsch an der Auflösung mit einem Objekt mit mehreren Eigenschaften? Scheint eine einfache Möglichkeit zu sein, mehrere Werte aus einer Auflösung herauszuholen.
jfriend00
4
Es ist vollkommen in Ordnung, das zu tun
Benjamin Gruenbaum
Sie können auch versprechen, dass .spread()Bluebird in dieser verwandten Antwort angezeigt wird
Sawtaytoes
Das Verhalten von Promise.all () scheint dem zu widersprechen. Promise.all([a, b, c]).then(function(x, y, z) {...})funktioniert in allen modernen Javascript-Engines korrekt, wobei x, y und z die aufgelösten Werte von a, b und c auswerten. Es ist also genauer zu sagen, dass die Sprache es Ihnen nicht einfach (oder vernünftig) macht, dies aus dem Benutzercode heraus zu tun (da Sie ein Versprechen direkt aus einer then-Klausel zurückgeben können, können Sie Ihre Werte in Versprechen einschließen und diese dann mit Versprechen einschließen .all (), um das gewünschte Verhalten zu erhalten, wenn auch auf verschlungene Weise).
Austin Hemmelgarn
3
@AustinHemmelgarn Das ist einfach falsch, Promise.allerfüllt mit einem Array . In Promise.all([a,b]).then((a, b) => bist undefined. Deshalb müssen Sie tun, .then(([a, b]) =>was eine destrukturierende Aufgabe ist
Benjamin Gruenbaum
38

Sie können nur einen Wert übergeben, es kann sich jedoch auch um ein Array mit mehreren Werten handeln:

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

Auf der anderen Seite können Sie den Destrukturierungsausdruck für ES2015 verwenden, um die einzelnen Werte abzurufen.

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

beide Versprechen nennen, verketten:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})
Alejandro Silva
quelle
5
Es heißt Destrukturierung , nicht "Dekonstruktor", und es ist kein Operator: - /
Bergi
3
Übrigens können Sie die Destrukturierung direkt in den Parametern verwenden: function step2([server, data]) { …- Auf diese Weise vermeiden Sie auch die Zuordnung zu impliziten Globalen. Und Sie sollten wirklich returnoder Promise.resolvenicht den new PromiseKonstruktor in Ihren Beispielen verwenden.
Bergi
Danke an @Bergi für die Empfehlungen!
Alejandro Silva
19

Sie können ein Objekt zurückgeben, das beide Werte enthält - daran ist nichts auszusetzen.

Eine andere Strategie besteht darin , den Wert über Schließungen zu erhalten, anstatt ihn weiterzugeben :

somethingAsync().then(afterSomething);

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

Vollständige statt teilweise inlinierte Form (äquivalent, wohl konsistenter):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}
Kevin Reid
quelle
3
Ist es in Ordnung, ein thenanderes zurückzugeben then? Es ist kein Anti-Muster ?
Robe007
Wie @ robe007 sagte, wäre das nicht ähnlich wie 'Callback Hell'? Hier blockiert Ihre Verschachtelung dann anstelle von Rückruffunktionen. Dies würde den eigentlichen Zweck von Versprechungen
zunichte machen
5

Zwei Dinge, die Sie tun können, ein Objekt zurückzugeben

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

Verwenden Sie das Zielfernrohr!

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

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

Hier ist, wie ich denke, dass Sie tun sollten.

die Kette teilen

Da beide Funktionen erstaunlichDaten verwenden , ist es sinnvoll, sie in einer dedizierten Funktion zu haben. Normalerweise mache ich das jedes Mal, wenn ich einige Daten wiederverwenden möchte, sodass sie immer als Funktionsargument vorhanden sind.

Da in Ihrem Beispiel Code ausgeführt wird, wird angenommen, dass alles in einer Funktion deklariert ist. Ich werde es toto () nennen . Dann haben wir eine weitere Funktion, die sowohl afterSomething () als auch afterSomethingElse () ausführt .

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

Sie werden auch feststellen, dass ich eine Rückgabeerklärung hinzugefügt habe , da dies normalerweise der richtige Weg für Versprechen ist. Sie geben immer ein Versprechen zurück, damit wir bei Bedarf weiter verketten können. Hier wird etwas Async () erstaunliche Daten erzeugen und es wird überall in der neuen Funktion verfügbar sein.

Wie diese neue Funktion aussehen wird, hängt normalerweise davon ab, ob processAsync () auch asynchron ist .

processAsync nicht asynchron

Kein Grund, Dinge zu komplizieren, wenn processAsync () nicht asynchron ist. Ein alter guter sequentieller Code würde es schaffen.

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

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

Beachten Sie, dass es keine Rolle spielt, ob afterSomethingElse () etwas Asynchrones tut oder nicht. In diesem Fall wird ein Versprechen zurückgegeben und die Kette kann fortgesetzt werden. Ist dies nicht der Fall, wird der Ergebniswert zurückgegeben. Da die Funktion jedoch von then () aufgerufen wird , wird der Wert ohnehin in ein Versprechen eingeschlossen (zumindest in rohem Javascript).

processAsync asynchron

Wenn processAsync () asynchron ist, sieht der Code etwas anders aus. Hier betrachten wir, dass afterSomething () und afterSomethingElse () nirgendwo anders wiederverwendet werden.

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

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

Gleich wie zuvor für afterSomethingElse () . Es kann asynchron sein oder nicht. Ein Versprechen wird zurückgegeben oder ein Wert in ein gelöstes Versprechen eingewickelt.


Ihr Codierungsstil kommt dem, was ich mache, ziemlich nahe, deshalb habe ich auch nach 2 Jahren geantwortet. Ich bin kein großer Fan von anonymen Funktionen überall. Ich finde es schwer zu lesen. Auch wenn es in der Gemeinde durchaus üblich ist. Es ist so, als hätten wir die Rückruf-Hölle durch ein Versprechen-Fegefeuer ersetzt .

Ich halte auch gerne den Namen der Funktionen in der dann kurz. Sie werden sowieso nur lokal definiert. Und die meiste Zeit rufen sie eine andere Funktion auf, die an anderer Stelle definiert ist - also wiederverwendbar -, um die Arbeit zu erledigen. Ich mache das sogar für Funktionen mit nur 1 Parameter, so dass ich die Funktion nicht ein- und ausschalten muss, wenn ich der Funktionssignatur einen Parameter hinzufüge / entferne.

Beispiel essen

Hier ist ein Beispiel:

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

Konzentrieren Sie sich nicht zu sehr auf Promise.resolve () . Es ist nur ein schneller Weg, um ein gelöstes Versprechen zu erstellen. Was ich damit erreichen möchte, ist, den gesamten Code, den ich ausführe, an einem einzigen Ort zu haben - direkt unter den Thens . Alle anderen Funktionen mit einem aussagekräftigeren Namen können wiederverwendet werden.

Der Nachteil dieser Technik ist, dass sie viele Funktionen definiert. Aber ich fürchte, es ist ein notwendiger Schmerz, um zu vermeiden, überall anonyme Funktionen zu haben. Und was ist das Risiko überhaupt: ein Stapelüberlauf? (Scherz!)


Die Verwendung von Arrays oder Objekten, wie in anderen Antworten definiert, würde ebenfalls funktionieren. Dies ist in gewisser Weise die Antwort von Kevin Reid .

Sie können auch bind () oder Promise.all () verwenden . Beachten Sie, dass Sie weiterhin Ihren Code teilen müssen.

mit bind

Wenn Sie Ihre Funktionen wiederverwendbar halten wollen , aber nicht wirklich brauchen , zu halten , was im Inneren der ist dann sehr kurz, können Sie bind () .

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

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

Um es einfach zu halten, wird bind () der Liste der Argumente (mit Ausnahme der ersten) beim Aufruf vor die Funktion stellen.

mit Promise.all

In Ihrem Beitrag haben Sie die Verwendung von verbreiten () erwähnt . Ich habe das von Ihnen verwendete Framework nie verwendet, aber hier ist, wie Sie es verwenden können sollten.

Einige glauben, dass Promise.all () die Lösung für alle Probleme ist, daher sollte es wohl erwähnt werden.

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

Sie können Daten an Promise.all () übergeben - beachten Sie das Vorhandensein des Arrays - solange Versprechen vorliegen. Stellen Sie jedoch sicher, dass keines der Versprechen fehlschlägt, da sonst die Verarbeitung beendet wird.

Und anstatt neue Variablen aus dem Argument args zu definieren , sollten Sie in der Lage sein , pread () anstelle von then () für alle Arten von großartiger Arbeit zu verwenden.

Gabriel
quelle
3

Erstellen Sie einfach ein Objekt und extrahieren Sie Argumente aus diesem Objekt.

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

Ziehen Sie Argumente aus versprechenResolution.

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

quelle
2

Was auch immer Sie von einem Versprechen zurückgeben, wird in ein Versprechen eingewickelt, das in der nächsten .then()Phase ausgepackt werden soll .

Es wird interessant, wenn Sie ein oder mehrere Versprechen neben einem oder mehreren synchronen Werten zurückgeben müssen, wie z.

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

In diesen Fällen wäre es wichtig, zu verwenden und Versprechen in der nächsten Phase ausgepackt Promise.all()zu bekommen, wie zp1p2.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 */);
Reduzieren
quelle
-1

Sie können ein Array bereitstellen und eine Funktion in Ihrer Auflösung ausführen

new Promise((resolve, reject) => {
    setTimeout(() => {
        const customfunction = () => {
            setinitialValues(values);
            resetForm({ ...values, status : { 
                apiErrors : {
                    first_name: "This field may not be blank.",
                    last_name : "Some thing"
                }
            }})
        }
        resolve([customfunction()])
    }, 2000)
})
Schamseer Ahammed
quelle