webentwicklung-frage-antwort-db.com.de

Versprechen Sie Rückrufe, die Versprechen zurückgeben

In Bezug auf diese beiden großartigen Quellen: NZakas - Rückgabe von Versprechungen in Versprechungsketten und MDN-Versprechungen möchte ich Folgendes fragen:

Wie wird der Wert jedes Mal, wenn wir einen Wert von einem Versprechenerfüllungsbearbeiter zurückgeben, an das neue Versprechen weitergereicht, das von demselben Bearbeiter zurückgegeben wurde?

Zum Beispiel,

let p1 = new Promise(function(resolve, reject) {
    resolve(42);
});

let p2 = new Promise(function(resolve, reject) {
    resolve(43);
});

let p3 = p1.then(function(value) {
    // first fulfillment handler
    console.log(value);     // 42
    return p2;
});

p3.then(function(value) {
    // second fulfillment handler
    console.log(value);     // 43
});

In diesem Beispiel ist p2 Ein Versprechen. p3 Ist auch ein Versprechen, das vom Fulfillment-Handler von p1 Stammt. Jedoch p2 !== p3. Stattdessen wird p2 Auf magische Weise in 43 Aufgelöst (wie?) Und dieser Wert wird dann an den Fulfillment-Handler von p3 Übergeben. Sogar der Satz hier ist verwirrend.

Könnten Sie mir bitte erklären, was genau hier los ist? Ich bin total verwirrt über dieses Konzept.

53
kstratis

Nehmen wir an, dass durch das Werfen von then() Callback das Ergebnisversprechen mit einem Fehler zurückgewiesen wird und durch das Zurückkehren von then() Callback das Ergebnisversprechen mit einem Erfolgswert erfüllt wird.

_let p2 = p1.then(() => {
  throw new Error('lol')
})
// p2 was rejected with Error('lol')

let p3 = p1.then(() => {
  return 42
})
// p3 was fulfilled with 42
_

Aber manchmal wissen wir auch innerhalb der Fortsetzung nicht, ob es uns gelungen ist oder nicht. Wir brauchen mehr Zeit.

_return checkCache().then(cachedValue => {
  if (cachedValue) {
    return cachedValue
  }

  // I want to do some async work here
})
_

Wenn ich dort jedoch asynchron arbeite, ist es zu spät für return oder throw, nicht wahr?

_return checkCache().then(cachedValue => {
  if (cachedValue) {
    return cachedValue
  }

  fetchData().then(fetchedValue => {
    // Doesn’t make sense: it’s too late to return from outer function by now.
    // What do we do?

    // return fetchedValue
  })
})
_

Aus diesem Grund wären Versprechen nicht nützlich, wenn Sie nicht in ein anderes Versprechen auflösen könnten .

Dies bedeutet nicht, dass in Ihrem Beispiel _p2_ wird _p3_ Sie sind separate Versprechungsobjekte. Wenn Sie jedoch _p2_ von then() zurückgeben, das _p3_ erzeugt, sagen Sie “Ich möchte, dass _p3_ zu was auch immer _p2_ aufgelöst wird. entscheidet, ob es erfolgreich ist oder fehlschlägt. “

wie Dies geschieht implementierungsspezifisch. Intern können Sie sich then() als das Erstellen eines neuen Versprechens vorstellen. Die Implementierung kann es jederzeit erfüllen oder ablehnen. Normalerweise wird es automatisch erfüllt oder abgelehnt, wenn Sie zurückkehren:

_// Warning: this is just an illustration
// and not a real implementation code.
// For example, it completely ignores
// the second then() argument for clarity,
// and completely ignores the Promises/A+
// requirement that continuations are
// run asynchronously.

then(callback) {
  // Save these so we can manipulate
  // the returned Promise when we are ready
  let resolve, reject

  // Imagine this._onFulfilled is an internal
  // queue of code to run after current Promise resolves.
  this._onFulfilled.Push(() => {
    let result, error, succeeded
    try {
      // Call your callback!
      result = callback(this._result)
      succeeded = true
    } catch (err) {
      error = err
      succeeded = false
    }

    if (succeeded) {
      // If your callback returned a value,
      // fulfill the returned Promise to it
      resolve(result)
    } else {
      // If your callback threw an error,
      // reject the returned Promise with it
      reject(error)
    }
  })

  // then() returns a Promise
  return new Promise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })
}
_

Auch dies ist sehr viel Pseudo-Code, zeigt aber die Idee dahinter, wie then() in Promise-Implementierungen implementiert werden könnte.

Wenn wir Unterstützung für das Auflösen eines Versprechens hinzufügen möchten, müssen wir den Code nur so ändern, dass er eine spezielle Verzweigung aufweist, wenn das callback, das Sie an then() übergeben, ein Versprechen zurückgegeben hat:

_    if (succeeded) {
      // If your callback returned a value,
      // resolve the returned Promise to it...
      if (typeof result.then === 'function') {
        // ...unless it is a Promise itself,
        // in which case we just pass our internal
        // resolve and reject to then() of that Promise
        result.then(resolve, reject)
      } else {
        resolve(result)
      }
    } else {
      // If your callback threw an error,
      // reject the returned Promise with it
      reject(error)
    }
  })
_

Lassen Sie mich noch einmal klarstellen, dass dies keine eigentliche Promise-Implementierung ist und große Lücken und Inkompatibilitäten aufweist. Es sollte Ihnen jedoch eine intuitive Vorstellung davon geben, wie Promise-Bibliotheken das Auflösen in ein Promise implementieren. Nachdem Sie mit der Idee vertraut sind, empfehle ich Ihnen, einen Blick auf die tatsächlichen Implementierungen von Promise zu werfen erledigen Sie dies .

38
Dan Abramov

Grundsätzlich ist p3return- ein weiteres Versprechen: p2. Das bedeutet, dass das Ergebnis von p2 Als Parameter an den nächsten Rückruf von then übergeben wird. In diesem Fall wird es in 43 Aufgelöst.

Immer wenn Sie das Schlüsselwort return verwenden, übergeben Sie das Ergebnis als Parameter an den Rückruf des nächsten then.

let p3 = p1.then(function(value) {
    // first fulfillment handler
    console.log(value);     // 42
    return p2;
});

Dein Code :

p3.then(function(value) {
    // second fulfillment handler
    console.log(value);     // 43
});

Entspricht:

p1.then(function(resultOfP1) {
    // resultOfP1 === 42
    return p2; // // Returning a promise ( that might resolve to 43 or fail )
})
.then(function(resultOfP2) {
    console.log(resultOfP2) // '43'
});

Übrigens ist mir aufgefallen, dass Sie die ES6-Syntax verwenden. Sie können eine leichtere Syntax verwenden, indem Sie die Fat Arrow-Syntax verwenden:

p1.then(resultOfP1 => p2) // the `return` is implied since it's a one-liner
.then(resultOfP2 => console.log(resultOfP2)); 
18
Vincent Taing

In diesem Beispiel ist p2 ein Versprechen. p3 ist auch ein Versprechen, das vom Fulfillment-Handler von p1 stammt. Jedoch p2! == p3. Stattdessen wird p2 auf magische Weise in 43 aufgelöst (wie?) Und dieser Wert wird dann an den Fulfillment-Handler von p3 übergeben. Sogar der Satz hier ist verwirrend.

eine vereinfachte Version wie das funktioniert (nur Pseudocode)

function resolve(value){
    if(isPromise(value)){
        value.then(resolve, reject);
    }else{
        //dispatch the value to the listener
    }
}

das Ganze ist etwas komplizierter, da man aufpassen muss, ob das Versprechen bereits eingelöst wurde und noch ein paar Dinge.

4
Thomas

Ich werde versuchen, die Frage zu beantworten "warum then Rückrufe Promises selbst zurückgeben können" kanonischer. Um einen anderen Blickwinkel einzunehmen, vergleiche ich Promises mit einem weniger komplexen und verwirrenden Containertyp - Arrays.

Ein Promise ist ein Container für einen zukünftigen Wert. Ein Array ist ein Container für eine beliebige Anzahl von Werten.

Wir können keine normalen Funktionen auf Containertypen anwenden:

const sqr = x => x * x;
const xs = [1,2,3];
const p = Promise.resolve(3);

sqr(xs); // fails
sqr(p); // fails

Wir brauchen einen Mechanismus, um sie in den Kontext eines bestimmten Containers zu heben:

xs.map(sqr); // [1,4,9]
p.then(sqr); // Promise {[[PromiseValue]]: 9}

Aber was passiert, wenn die bereitgestellte Funktion selbst einen Container desselben Typs zurückgibt?

const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];
const p = Promise.resolve(3);

xs.map(sqra); // [[1],[4],[9]]
p.then(sqrp); // Promise {[[PromiseValue]]: 9}

sqra verhält sich wie erwartet. Es wird nur ein verschachtelter Container mit den richtigen Werten zurückgegeben. Dies ist jedoch offensichtlich nicht sehr nützlich.

Aber wie kann das Ergebnis von sqrp interpretiert werden? Wenn wir unserer eigenen Logik folgen, musste es so etwas wie Promise {[[PromiseValue]]: Promise {[[PromiseValue]]: 9}} Sein - ist es aber nicht. Also, welche Magie geht hier vor?

Um den Mechanismus zu rekonstruieren, müssen wir lediglich unsere map -Methode etwas anpassen:

const flatten = f => x => f(x)[0];
const sqra = x => [x * x];
const sqrp = x => Promise.resolve(x * x);
const xs = [1,2,3];

xs.map(flatten(sqra))

flatten nimmt nur eine Funktion und einen Wert, wendet die Funktion auf den Wert an und packt das Ergebnis aus, wodurch eine verschachtelte Array-Struktur um eine Ebene reduziert wird.

Einfach ausgedrückt, entspricht then im Kontext von Promises map kombiniert mit flatten im Kontext von Arrays. Dieses Verhalten ist äußerst wichtig. Wir können nicht nur normale Funktionen auf ein Promise anwenden, sondern auch Funktionen, die selbst ein Promise zurückgeben.

Tatsächlich ist dies die Domäne der funktionalen Programmierung. Ein Promise ist eine spezifische Implementierung eines monad, then ist bind/chain und eine Funktion, die ein Promise ist eine monadische Funktion. Wenn Sie die Promise API verstehen, verstehen Sie im Grunde alle Monaden.

3
user6445533