Wie wird eine Versprechen / Zurückstellen-Bibliothek implementiert? [geschlossen]

74

Wie wird eine Versprechen / Zurückstellen-Bibliothek wie q implementiert? Ich habe versucht, den Quellcode zu lesen, fand es aber ziemlich schwer zu verstehen. Daher fand ich es großartig, wenn mir jemand auf hoher Ebene erklären könnte, mit welchen Techniken Versprechen in JS-Umgebungen mit einem Thread umgesetzt werden wie Node und Browser.

Derek Chiang
quelle

Antworten:

149

Ich finde es schwieriger zu erklären, als ein Beispiel zu zeigen. Hier ist eine sehr einfache Implementierung dessen, was ein Aufschub / Versprechen sein könnte.

Haftungsausschluss: Dies ist keine funktionale Implementierung und einige Teile der Promise / A-Spezifikation fehlen. Dies dient nur zur Erläuterung der Grundlage der Versprechen.

tl; dr: Gehen Sie zum Abschnitt Klassen und Beispiel erstellen , um die vollständige Implementierung zu sehen.

Versprechen:

Zuerst müssen wir ein Versprechungsobjekt mit einer Reihe von Rückrufen erstellen. Ich werde mit Objekten arbeiten, weil es klarer ist:

var promise = {
  callbacks: []
}

Fügen Sie jetzt Rückrufe mit der Methode hinzu und dann:

var promise = {
  callbacks: [],
  then: function (callback) {
    callbacks.push(callback);
  }
}

Und wir brauchen auch die Fehlerrückrufe:

var promise = {
  okCallbacks: [],
  koCallbacks: [],
  then: function (okCallback, koCallback) {
    okCallbacks.push(okCallback);
    if (koCallback) {
      koCallbacks.push(koCallback);
    }
  }
}

Verschieben:

Erstellen Sie nun das verzögerte Objekt, das ein Versprechen hat:

var defer = {
  promise: promise
};

Der Aufschub muss behoben werden:

var defer = {
  promise: promise,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },
};

Und muss ablehnen:

var defer = {
  promise: promise,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },

  reject: function (error) {
    this.promise.koCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(error)
      }, 0);
    });
  }
};

Beachten Sie, dass die Rückrufe in einem Timeout aufgerufen werden, damit der Code immer asynchron ist.

Und das ist es, was eine grundlegende Defer / Promise-Implementierung benötigt.

Klassen und Beispiel erstellen:

Lassen Sie uns nun beide Objekte in Klassen konvertieren, zunächst das Versprechen:

var Promise = function () {
  this.okCallbacks = [];
  this.koCallbacks = [];
};

Promise.prototype = {
  okCallbacks: null,
  koCallbacks: null,
  then: function (okCallback, koCallback) {
    okCallbacks.push(okCallback);
    if (koCallback) {
      koCallbacks.push(koCallback);
    }
  }
};

Und jetzt der Aufschub:

var Defer = function () {
  this.promise = new Promise();
};

Defer.prototype = {
  promise: null,
  resolve: function (data) {
    this.promise.okCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(data)
      }, 0);
    });
  },

  reject: function (error) {
    this.promise.koCallbacks.forEach(function(callback) {
      window.setTimeout(function () {
        callback(error)
      }, 0);
    });
  }
};

Und hier ist ein Anwendungsbeispiel:

function test() {
  var defer = new Defer();
  // an example of an async call
  serverCall(function (request) {
    if (request.status === 200) {
      defer.resolve(request.responseText);
    } else {
      defer.reject(new Error("Status code was " + request.status));
    }
  });
  return defer.promise;
}

test().then(function (text) {
  alert(text);
}, function (error) {
  alert(error.message);
});

Wie Sie sehen können, sind die grundlegenden Teile einfach und klein. Es wächst, wenn Sie andere Optionen hinzufügen, z. B. die Auflösung mehrerer Versprechen:

Defer.all(promiseA, promiseB, promiseC).then()

oder Versprechen verketten:

getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);

Weitere Informationen zu den Spezifikationen: CommonJS Promise Specification . Beachten Sie, dass die Hauptbibliotheken (Q, when.js, rsvp.js, Node-Promise, ...) der Promises / A- Spezifikation entsprechen.

Hoffe ich war klar genug.

Bearbeiten:

Wie in den Kommentaren gefragt, habe ich in dieser Version zwei Dinge hinzugefügt:

  • Die Möglichkeit, dann ein Versprechen einzuholen, egal welchen Status es hat.
  • Die Möglichkeit, Versprechen zu verketten.

Um das Versprechen bei Auflösung aufrufen zu können, müssen Sie den Status zum Versprechen hinzufügen. Wenn das Versprechen aufgerufen wird, überprüfen Sie diesen Status. Wenn der Status aufgelöst oder abgelehnt wird, führen Sie einfach den Rückruf mit seinen Daten oder Fehlern aus.

Um Versprechen verketten zu können, müssen Sie für jeden Anruf einen neuen Aufschub generieren thenund, wenn das Versprechen aufgelöst / abgelehnt wird, das neue Versprechen mit dem Ergebnis des Rückrufs auflösen / ablehnen. Wenn das Versprechen erfüllt ist und der Rückruf ein neues Versprechen zurückgibt, ist er an das mit dem zurückgegebene Versprechen gebunden then(). Wenn nicht, wird das Versprechen mit dem Ergebnis des Rückrufs gelöst.

Hier ist das Versprechen:

var Promise = function () {
  this.okCallbacks = [];
  this.koCallbacks = [];
};

Promise.prototype = {
  okCallbacks: null,
  koCallbacks: null,
  status: 'pending',
  error: null,

  then: function (okCallback, koCallback) {
    var defer = new Defer();

    // Add callbacks to the arrays with the defer binded to these callbacks
    this.okCallbacks.push({
      func: okCallback,
      defer: defer
    });

    if (koCallback) {
      this.koCallbacks.push({
        func: koCallback,
        defer: defer
      });
    }

    // Check if the promise is not pending. If not call the callback
    if (this.status === 'resolved') {
      this.executeCallback({
        func: okCallback,
        defer: defer
      }, this.data)
    } else if(this.status === 'rejected') {
      this.executeCallback({
        func: koCallback,
        defer: defer
      }, this.error)
    }

    return defer.promise;
  },

  executeCallback: function (callbackData, result) {
    window.setTimeout(function () {
      var res = callbackData.func(result);
      if (res instanceof Promise) {
        callbackData.defer.bind(res);
      } else {
        callbackData.defer.resolve(res);
      }
    }, 0);
  }
};

Und der Aufschub:

var Defer = function () {
  this.promise = new Promise();
};

Defer.prototype = {
  promise: null,
  resolve: function (data) {
    var promise = this.promise;
    promise.data = data;
    promise.status = 'resolved';
    promise.okCallbacks.forEach(function(callbackData) {
      promise.executeCallback(callbackData, data);
    });
  },

  reject: function (error) {
    var promise = this.promise;
    promise.error = error;
    promise.status = 'rejected';
    promise.koCallbacks.forEach(function(callbackData) {
      promise.executeCallback(callbackData, error);
    });
  },

  // Make this promise behave like another promise:
  // When the other promise is resolved/rejected this is also resolved/rejected
  // with the same data
  bind: function (promise) {
    var that = this;
    promise.then(function (res) {
      that.resolve(res);
    }, function (err) {
      that.reject(err);
    })
  }
};

Wie Sie sehen können, ist es ziemlich gewachsen.

Kaizo
quelle
1
Es wäre wahrscheinlich gut, die CommonJS Promises / A-Vorschläge zu verknüpfen ... für diejenigen, die dieses Muster verstanden haben und tiefer gehen wollen :)
Gustavohenke
1
Vielen Dank. Link zu allen Spezifikationen hinzugefügt.
Kaizo
2
Ihr Beispiel funktioniert absolut nicht. Weder wird der Versprechen-Status festgelegt (Ihr Status kann gleichzeitig erfüllt und abgelehnt werden), noch werden Rückrufe nach der Lösung hinzugefügt, noch wird ein thenanderes Versprechen zurückgegeben (was wichtig ist).
Bergi
2
Dies ist ein extrem einfaches Beispiel, keine voll funktionsfähige Implementierung. Ich weiß, dass einige Teile der Spezifikationen nicht implementiert sind und dass es möglicherweise nicht funktioniert, sondern nur, um die Grundlage von Versprechungen zu erklären.
Kaizo
Ja, ich sehe das und eine Vereinfachung ist für eine gute Antwort erforderlich (meine ist weder vollständig konform). Ich denke jedoch, dass es an den Hauptteilen einer versprochenen Umsetzung mangelt . Das wiederholte Wiederholen des gleichen Codes macht die Antwort nur länger nicht besser :-(
Bergi
7

Q ist eine sehr komplexe Versprechungsbibliothek in Bezug auf die Implementierung, da sie Pipelining- und RPC-Szenarien unterstützen soll. Ich habe meine eigene sehr nackten Knochen Umsetzung der Versprechen / A + Spezifikation hier .

Im Prinzip ist es ganz einfach. Bevor das Versprechen erfüllt / gelöst wird, führen Sie Aufzeichnungen über Rückrufe oder Fehler, indem Sie sie in ein Array verschieben. Wenn das Versprechen erfüllt ist, rufen Sie die entsprechenden Rückrufe oder Fehler an und zeichnen auf, mit welchem ​​Ergebnis das Versprechen erfüllt wurde (und ob es erfüllt oder abgelehnt wurde). Nachdem es erledigt ist, rufen Sie einfach die Rückrufe oder Fehler mit dem gespeicherten Ergebnis auf.

Das gibt Ihnen ungefähr die Semantik von done. Um zu erstellen, müssen thenSie nur ein neues Versprechen zurückgeben, das mit dem Ergebnis des Aufrufs der Rückrufe / Fehler aufgelöst wird.

Wenn Sie an einer vollständigen Erläuterung der Gründe für die Entwicklung einer vollständigen On-Promise-Implementierung mit Unterstützung für RPC und Pipelining wie Q interessiert sind, können Sie die Argumentation von kriskowal hier lesen . Es ist ein wirklich schöner abgestufter Ansatz, den ich nicht genug empfehlen kann, wenn Sie daran denken, Versprechen umzusetzen. Es ist wahrscheinlich eine Lektüre wert, auch wenn Sie nur eine Versprechensbibliothek verwenden.

ForbesLindesay
quelle
2
Wow, dieser Artikel von @KrisKowal ist großartig. Er sollte es als Antwort posten, um Dutzende von Upvotes zu erhalten :-)
Bergi
1
In der Tat ist es brillant, ich habe vor, es als richtige Webseite neu zu formatieren, damit es irgendwann etwas besser formatiert wird.
ForbesLindesay
+1 für den KrisKowal-Artikel. Tolle Lektüre.
Derek Chiang
Link scheint unterbrochen zu sein ...
TJ
6

Wie Forbes in seiner Antwort erwähnt, habe ich viele der Entwurfsentscheidungen für eine Bibliothek wie Q aufgezeichnet, hier https://github.com/kriskowal/q/tree/v1/design . Es genügt zu sagen, dass es Ebenen einer Versprechensbibliothek gibt und viele Bibliotheken, die auf verschiedenen Ebenen anhalten.

Auf der ersten Ebene, die von der Promises / A + -Spezifikation erfasst wird, ist ein Versprechen ein Proxy für ein eventuelles Ergebnis und eignet sich zur Verwaltung der „lokalen Asynchronität“ . Das heißt, es ist geeignet, um sicherzustellen, dass die Arbeit in der richtigen Reihenfolge ausgeführt wird, und um sicherzustellen, dass es einfach und unkompliziert ist, auf das Ergebnis einer Operation zu warten, unabhängig davon, ob sie bereits erledigt ist oder in Zukunft stattfinden wird. Ebenso einfach ist es für eine oder mehrere Parteien, ein letztendliches Ergebnis zu abonnieren.

Q, wie ich es implementiert habe, bietet Versprechen, die Proxys für eventuelle, entfernte oder eventuelle + entfernte Ergebnisse sind. Zu diesem Zweck wird das Design umgekehrt, mit verschiedenen Implementierungen für Versprechen - zurückgestellte Versprechen, erfüllte Versprechen, abgelehnte Versprechen und Versprechen für entfernte Objekte (das letzte wird in Q-Connection implementiert). Sie alle haben dieselbe Benutzeroberfläche und senden und empfangen Nachrichten wie "dann" (was für Promises / A + ausreicht), aber auch "get" und "invoke". Q handelt also von „verteilter Asynchronität“ und existiert auf einer anderen Ebene.

Q wurde jedoch tatsächlich von einer höheren Ebene entfernt, wo Versprechen zur Verwaltung der verteilten Asynchronität zwischen gegenseitig verdächtigen Parteien wie Ihnen, einem Händler, einer Bank, Facebook, der Regierung verwendet werden - keine Feinde, vielleicht sogar Freunde, aber manchmal mit Konflikten von Interesse. Das von mir implementierte Q ist so konzipiert, dass es API-kompatibel mit gehärteten Sicherheitsversprechen ist (was der Grund für die Trennung von promiseund ist resolve), mit der Hoffnung, dass es Menschen in Versprechen einführt, sie in der Verwendung dieser API schult und ihnen ermöglicht, ihren Code zu übernehmen mit ihnen, wenn sie in Zukunft Versprechen in sicheren Mashups verwenden müssen.

Natürlich gibt es Kompromisse, wenn Sie die Ebenen nach oben bewegen, normalerweise in der Geschwindigkeit. So können Versprechen-Implementierungen auch so gestaltet werden, dass sie nebeneinander existieren. Hier setzt das Konzept eines „Thenable“ an. Versprechensbibliotheken auf jeder Ebene können so gestaltet werden, dass sie Versprechungen von jeder anderen Ebene verbrauchen, sodass mehrere Implementierungen nebeneinander existieren können und Benutzer nur das kaufen können, was sie benötigen.

Trotzdem gibt es keine Entschuldigung dafür, schwer zu lesen zu sein. Domenic und ich arbeiten an einer Version von Q, die modularer und zugänglicher sein wird, wobei einige ihrer ablenkenden Abhängigkeiten und Problemumgehungen in andere Module und Pakete verschoben werden. Zum Glück haben Leute wie Forbes , Crockford und andere die Bildungslücke geschlossen, indem sie einfachere Bibliotheken geschaffen haben.

Kris Kowal
quelle
Link scheint unterbrochen zu sein ...
TJ
4

Stellen Sie zunächst sicher, dass Sie verstehen, wie Versprechen funktionieren sollen. Werfen Sie einen Blick auf die CommonJs Promises- Vorschläge und die Promises / A + -Spezifikation dafür.

Es gibt zwei grundlegende Konzepte, die jeweils in wenigen einfachen Zeilen implementiert werden können:

  • Ein Versprechen wird asynchron mit dem Ergebnis aufgelöst. Das Hinzufügen von Rückrufen ist eine transparente Aktion - unabhängig davon, ob das Versprechen bereits gelöst wurde oder nicht, werden sie mit dem Ergebnis aufgerufen, sobald es verfügbar ist.

    function Deferred() {
        var callbacks = [], // list of callbacks
            result; // the resolve arguments or undefined until they're available
        this.resolve = function() {
            if (result) return; // if already settled, abort
            result = arguments; // settle the result
            for (var c;c=callbacks.shift();) // execute stored callbacks
                c.apply(null, result);
        });
        // create Promise interface with a function to add callbacks:
        this.promise = new Promise(function add(c) {
            if (result) // when results are available
                c.apply(null, result); // call it immediately
            else
                callbacks.push(c); // put it on the list to be executed later
        });
    }
    // just an interface for inheritance
    function Promise(add) {
        this.addCallback = add;
    }
    
  • Versprechen haben eine thenMethode, mit der sie verkettet werden können. Ich nehme einen Rückruf entgegen und gebe ein neues Versprechen zurück, das mit dem Ergebnis dieses Rückrufs aufgelöst wird, nachdem es mit dem Ergebnis des ersten Versprechens aufgerufen wurde. Wenn der Rückruf ein Versprechen zurückgibt, wird es assimiliert, anstatt verschachtelt zu werden.

    Promise.prototype.then = function(fn) {
        var dfd = new Deferred(); // create a new result Deferred
        this.addCallback(function() { // when `this` resolves…
            // execute the callback with the results
            var result = fn.apply(null, arguments);
            // check whether it returned a promise
            if (result instanceof Promise)
                result.addCallback(dfd.resolve); // then hook the resolution on it
            else
                dfd.resolve(result); // resolve the new promise immediately 
            });
        });
        // and return the new Promise
        return dfd.promise;
    };
    

Weitere Konzepte wären die Beibehaltung eines separaten Fehlerzustands (mit einem zusätzlichen Rückruf) und das Abfangen von Ausnahmen in den Handlern oder die Gewährleistung der Asynchronität für die Rückrufe. Sobald Sie diese hinzugefügt haben, haben Sie eine voll funktionsfähige Promise-Implementierung.

Hier ist die Fehlersache ausgeschrieben. Es ist leider ziemlich repetitiv; Sie können es besser machen, indem Sie zusätzliche Verschlüsse verwenden, aber dann ist es wirklich sehr, sehr schwer zu verstehen.

function Deferred() {
    var callbacks = [], // list of callbacks
        errbacks = [], // list of errbacks
        value, // the fulfill arguments or undefined until they're available
        reason; // the error arguments or undefined until they're available
    this.fulfill = function() {
        if (reason || value) return false; // can't change state
        value = arguments; // settle the result
        for (var c;c=callbacks.shift();)
            c.apply(null, value);
        errbacks.length = 0; // clear stored errbacks
    });
    this.reject = function() {
        if (value || reason) return false; // can't change state
        reason = arguments; // settle the errror
        for (var c;c=errbacks.shift();)
            c.apply(null, reason);
        callbacks.length = 0; // clear stored callbacks
    });
    this.promise = new Promise(function add(c) {
        if (reason) return; // nothing to do
        if (value)
            c.apply(null, value);
        else
            callbacks.push(c);
    }, function add(c) {
        if (value) return; // nothing to do
        if (reason)
            c.apply(null, reason);
        else
            errbacks.push(c);
    });
}
function Promise(addC, addE) {
    this.addCallback = addC;
    this.addErrback = addE;
}
Promise.prototype.then = function(fn, err) {
    var dfd = new Deferred();
    this.addCallback(function() { // when `this` is fulfilled…
        try {
            var result = fn.apply(null, arguments);
            if (result instanceof Promise) {
                result.addCallback(dfd.fulfill);
                result.addErrback(dfd.reject);
            } else
                dfd.fulfill(result);
        } catch(e) { // when an exception was thrown
            dfd.reject(e);
        }
    });
    this.addErrback(err ? function() { // when `this` is rejected…
        try {
            var result = err.apply(null, arguments);
            if (result instanceof Promise) {
                result.addCallback(dfd.fulfill);
                result.addErrback(dfd.reject);
            } else
                dfd.fulfill(result);
        } catch(e) { // when an exception was re-thrown
            dfd.reject(e);
        }
    } : dfd.reject); // when no `err` handler is passed then just propagate
    return dfd.promise;
};
Bergi
quelle
Nicht erklären, wie es implementiert wird, und keine Verzögerung / Versprechen-Implementierung zeigen.
Kaizo
@Kaizo: Danke, ich habe nicht verstanden, dass OP speziell nach Verzögerungen fragt. Erläuterung hinzugefügt und zur verzögerten Schnittstelle gewechselt. Fehlt noch etwas Relevantes?
Bergi
Ich habe einige Zeit gebraucht, um zu verstehen, wie es funktioniert, obwohl es ziemlich klein und kommentiert ist. Ihre Version ist viel vollständiger als meine und ziemlich clever, aber auch viel komplizierter. Das einzige "wichtige", was ich vermisse, ist die Ablehnungsoption und der Fehlerrückruf.
Kaizo
@ Kaizo: Ja, ich habe das absichtlich weggelassen, weil der Code, wenn er präzise geschrieben wird, nur noch komplexer und weniger verständlich wird :-) Schauen Sie sich meine Bearbeitung an…
Bergi
In welchem ​​Fall wird die addCallbackMethode in der PromiseKlasse mehrmals aufgerufen? Die thenMethode gibt nur eine neue PromiseInstanz zurück. Warum also ein Rückruf-Array in der DeferredKlasse behalten ?
Towry
1

Vielleicht möchten Sie den Blog-Beitrag auf Adehun lesen.

Adehun ist eine extrem leichte Implementierung (ca. 166 LOC) und sehr nützlich, um zu lernen, wie die Promise / A + -Spezifikation implementiert wird.

Haftungsausschluss : Ich habe den Blog-Beitrag geschrieben, aber der Blog-Beitrag erklärt alles über Adehun.

Die Übergangsfunktion - Gatekeeper für den Zustandsübergang

Gatekeeper-Funktion; stellt sicher, dass Zustandsübergänge auftreten, wenn alle erforderlichen Bedingungen erfüllt sind.

Wenn die Bedingungen erfüllt sind, aktualisiert diese Funktion den Status und den Wert des Versprechens. Anschließend wird die Prozessfunktion zur weiteren Verarbeitung ausgelöst.

Die Prozessfunktion führt die richtige Aktion basierend auf dem Übergang aus (z. B. bis zur Erfüllung) und wird später erläutert.

function transition (state, value) {
  if (this.state === state ||
    this.state !== validStates.PENDING ||
    !isValidState(state)) {
      return;
    }

  this.value = value;
  this.state = state;
  this.process();
}

Die Dann-Funktion

Die then-Funktion akzeptiert zwei optionale Argumente (onFulfill- und onReject-Handler) und muss ein neues Versprechen zurückgeben. Zwei Hauptanforderungen:

  1. Das Basisversprechen (das dann aufgerufen wird) muss ein neues Versprechen unter Verwendung der übergebenen Handler erstellen. Die Basis speichert auch einen internen Verweis auf dieses erstellte Versprechen, sodass dieser aufgerufen werden kann, sobald das Basisversprechen erfüllt / abgelehnt wurde.

  2. Wenn das Basisversprechen erfüllt ist (dh erfüllt oder abgelehnt wurde), sollte sofort der entsprechende Handler angerufen werden. Adehun.js behandelt dieses Szenario, indem es den Prozess in der then-Funktion aufruft.

``

function then(onFulfilled, onRejected) {
    var queuedPromise = new Adehun();
    if (Utils.isFunction(onFulfilled)) {
        queuedPromise.handlers.fulfill = onFulfilled;
    }

    if (Utils.isFunction(onRejected)) {
        queuedPromise.handlers.reject = onRejected;
    }

    this.queue.push(queuedPromise);
    this.process();

    return queuedPromise;
}`

Die Prozessfunktion - Übergänge verarbeiten

Dies wird nach Zustandsübergängen oder beim Aufrufen der then-Funktion aufgerufen. Daher muss nach ausstehenden Versprechungen gesucht werden, da diese möglicherweise von der then-Funktion aufgerufen wurden.

Process führt das Promise Resolution-Verfahren für alle intern gespeicherten Versprechen aus (dh diejenigen, die über die then-Funktion an das Basisversprechen angehängt wurden) und erzwingt die folgenden Promise / A + -Anforderungen:

  1. Asynchrones Aufrufen der Handler mithilfe des Utils.runAsync-Hilfsprogramms (ein dünner Wrapper um setTimeout (setImmediate funktioniert auch)).

  2. Erstellen von Fallback-Handlern für die Handler onSuccess und onReject, falls diese fehlen.

  3. Auswahl der richtigen Handlerfunktion basierend auf dem Versprechungsstatus, z. B. erfüllt oder abgelehnt.

  4. Anwenden des Handlers auf den Wert des Basisversprechens. Der Wert dieser Operation wird an die Resolve-Funktion übergeben, um den Versprechen-Verarbeitungszyklus abzuschließen.

  5. Wenn ein Fehler auftritt, wird das beigefügte Versprechen sofort abgelehnt.

    function process () {var that = this, fillFallBack = function (value) {Rückgabewert; }, ablehnenFallBack = Funktion (Grund) {Grund werfen; };

    if (this.state === validStates.PENDING) {
        return;
    }
    
    Utils.runAsync(function() {
        while (that.queue.length) {
            var queuedP = that.queue.shift(),
                handler = null,
                value;
    
            if (that.state === validStates.FULFILLED) {
                handler = queuedP.handlers.fulfill ||
                    fulfillFallBack;
            }
            if (that.state === validStates.REJECTED) {
                handler = queuedP.handlers.reject ||
                    rejectFallBack;
            }
    
            try {
                value = handler(that.value);
            } catch (e) {
                queuedP.reject(e);
                continue;
            }
    
            Resolve(queuedP, value);
        }
    });
    

    }}

Die Funktion Auflösen - Versprechen auflösen

Dies ist wahrscheinlich der wichtigste Teil der Implementierung von Versprechen, da es sich um die Lösung von Versprechen handelt. Es akzeptiert zwei Parameter - das Versprechen und seinen Auflösungswert.

Zwar gibt es viele Überprüfungen für verschiedene mögliche Auflösungswerte; Die interessanten Auflösungsszenarien sind zwei - diejenigen, bei denen ein Versprechen übergeben wird und ein Thenable (ein Objekt mit einem Then-Wert).

  1. Einen Versprechenswert übergeben

Wenn der Auflösungswert ein weiteres Versprechen ist, muss das Versprechen den Status dieses Auflösungswerts annehmen. Da dieser Auflösungswert ausstehend oder abgerechnet werden kann, besteht der einfachste Weg, einen neuen Handler an den Auflösungswert anzuhängen und das darin enthaltene ursprüngliche Versprechen zu verarbeiten. Wann immer es sich einpendelt, wird das ursprüngliche Versprechen gelöst oder abgelehnt.

  1. Übergabe eines Wertes

Der Haken dabei ist, dass die then-Funktion des thenable-Werts nur einmal aufgerufen werden muss (eine gute Verwendung für den einmaligen Wrapper aus der funktionalen Programmierung). Wenn das Abrufen der Funktion then eine Ausnahme auslöst, ist das Versprechen ebenfalls sofort abzulehnen.

Wie zuvor wird die then-Funktion mit Funktionen aufgerufen, die das Versprechen letztendlich auflösen oder ablehnen. Der Unterschied besteht jedoch darin, dass das aufgerufene Flag beim ersten Aufruf gesetzt wird und nachfolgende Aufrufe keine Operationen ausführen.

function Resolve(promise, x) {
  if (promise === x) {
    var msg = "Promise can't be value";
    promise.reject(new TypeError(msg));
  }
  else if (Utils.isPromise(x)) {
    if (x.state === validStates.PENDING){
      x.then(function (val) {
        Resolve(promise, val);
      }, function (reason) {
        promise.reject(reason);
      });
    } else {
      promise.transition(x.state, x.value);
    }
  }
  else if (Utils.isObject(x) ||
           Utils.isFunction(x)) {
    var called = false,
        thenHandler;

    try {
      thenHandler = x.then;

      if (Utils.isFunction(thenHandler)){
        thenHandler.call(x,
          function (y) {
            if (!called) {
              Resolve(promise, y);
              called = true;
            }
          }, function (r) {
            if (!called) {
              promise.reject(r);
              called = true;
            }
       });
     } else {
       promise.fulfill(x);
       called = true;
     }
   } catch (e) {
     if (!called) {
       promise.reject(e);
       called = true;
     }
   }
 }
 else {
   promise.fulfill(x);
 }
}

Der Promise Constructor

Und dies ist derjenige, der alles zusammenfügt. Die Erfüllungs- und Zurückweisungsfunktionen sind syntaktischer Zucker, der No-Op-Funktionen zum Auflösen und Zurückweisen übergibt.

var Adehun = function (fn) {
 var that = this;

 this.value = null;
 this.state = validStates.PENDING;
 this.queue = [];
 this.handlers = {
   fulfill : null,
   reject : null
 };

 if (fn) {
   fn(function (value) {
     Resolve(that, value);
   }, function (reason) {
     that.reject(reason);
   });
 }
};

Ich hoffe, dies hat dazu beigetragen, mehr Licht in die Art und Weise zu bringen, wie Versprechen funktionieren.

AbdulFattah Popoola
quelle
Bitte posten Sie den Inhalt des Blogs in Ihrer Antwort oder zumindest den Kern davon. Links sind keine Antwort .
Bergi