Erstellen eines (ES6) Versprechens, ohne es zu lösen


77

Wie erstelle ich mithilfe von ES6-Versprechungen ein Versprechen, ohne die Logik für dessen Lösung zu definieren? Hier ist ein einfaches Beispiel (etwas TypeScript):

var promises = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return promises[key];
  }
  var promise = new Promise(resolve => {
    // But I don't want to try resolving anything here :(
  });

  promises[key] = promise;
  return promise;
}

function resolveWith(key: string, value: any): void {
  promises[key].resolve(value); // Not valid :(
}

Mit anderen Versprechensbibliotheken ist das ganz einfach. JQuery's zum Beispiel:

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = $.Deferred();    
  deferreds[key] = def;
  return def.promise();
}

function resolveWith(key: string, value: any): void {
  deferreds[key].resolve(value);
}

Die einzige Möglichkeit, dies zu tun, besteht darin, die Auflösungsfunktion irgendwo im Executor des Versprechens zu speichern, aber das scheint chaotisch zu sein, und ich bin nicht sicher, ob definiert ist, wann genau diese Funktion ausgeführt wird - wird sie immer sofort beim Aufbau ausgeführt?

Vielen Dank.


1
WTH würdest du so etwas machen? Ein Versprechen ohne Auflösungslogik ist ein für immer anstehendes Versprechen.
Bergi

Ihr zweiter Teil der Frage ist ein Duplikat von Is JavaScript Promise Callback ausgeführt Asynchronosuly
Bergi

1
@Bergi - Stellen Sie sich so etwas wie ein asynchrones Abhängigkeitsinjektionssystem vor. Sie haben einen Teil, in dem Sie Injektionsartikel registrieren, und einen anderen, in dem Sie sie anfordern. Wenn ich einen Artikel anfordere, der noch nicht registriert wurde, möchte ich ein Versprechen zurückgeben, das gelöst wird, sobald es vorliegt.
Barguast

Antworten:


91

Gute Frage!

Der an den Versprechen-Konstruktor übergebene Resolver wird absichtlich synchron ausgeführt, um diesen Anwendungsfall zu unterstützen:

var deferreds = [];
var p = new Promise(function(resolve, reject){
    deferreds.push({resolve: resolve, reject: reject});
});

Dann zu einem späteren Zeitpunkt:

 deferreds[0].resolve("Hello"); // resolve the promise with "Hello"

Der Grund, warum der Versprechungskonstruktor angegeben wird, ist folgender:

  • Typischerweise (aber nicht immer) ist die Auflösungslogik an die Erstellung gebunden.
  • Der Versprechen-Konstruktor ist sicher und wandelt Ausnahmen in Ablehnungen um.

Manchmal passt es nicht und dafür läuft der Resolver synchron. Hier ist verwandte Lektüre zum Thema .


Ich habe nach dem Vorbild Promise.resolveder Grenze gedacht und sie aufbewahrt then. Aber das sieht sauber aus und ich bin mir nicht mal sicher, ob die Bindung thenfunktionieren wird.
thefourtheye

Beeindruckend. Danke für die unglaublich schnelle und gründliche Antwort! Haben Sie zufällig eine Quelle, die besagt, dass der Executor (die Funktion mit den Argumenten zum Auflösen und Zurückweisen) zum Zeitpunkt der Erstellung immer synchron ausgeführt wird?
Barguast

1
@Barguast sicher, insbesondere für ES6 - ecma-international.org/ecma-262/6.0/… ist, wie Versprechen konstruiert werden, was ecma-international.org/ecma-262/6.0/index.html#sec-construct heißt synchron aufgerufen. Es wird wiederum synchron von ecma-international.org/ecma-262/6.0/… aufgerufen. Ich denke, die alte github.com/promises-aplus/constructor-spec ist eine bessere Quelle.
Benjamin Gruenbaum

2
@BenjaminGruenbaum: Zeigen Sie einfach auf Ist JavaScript Promise Callback Asynchronosuly ausgeführt :-)
Bergi

1
@EugeneHoza war es früher (vor langer Zeit), aber es wurde in das aufschlussreiche Konstruktormuster geändert (das als sicherer angesehen wurde). Früher habe ich es geliebt - aber im Nachhinein bin ich mir nicht sicher, ob es tatsächlich sicherer ist - es ist nur ein Kompromiss. Sie können hier darüber lesen: blog.domenic.me/the-revealing-constructor-pattern - außerdem, wenn Sie sich leidenschaftlich dafür fühlen (nachdem Sie natürlich den relevanten Hintergrund gelesen haben) -, können Sie gerne eine Vorschlag an TC39, eine weitere API einzuführen.
Benjamin Gruenbaum

51

Ich möchte hier meine 2 Cent hinzufügen. In Anbetracht der Frage " Erstellen eines es6-Versprechens, ohne es zu lösen " habe ich es gelöst, indem ich eine Wrapper-Funktion erstellt und stattdessen die Wrapper-Funktion aufgerufen habe . Code:

Angenommen, wir haben eine Funktion, fdie ein Versprechen zurückgibt

/** @return Promise<any> */
function f(args) {
   return new Promise(....)
}

// calling f()
f('hello', 42).then((response) => { ... })

Jetzt möchte ich einen Anruf vorbereiten , f('hello', 42)ohne ihn tatsächlich zu lösen:

const task = () => f('hello', 42) // not calling it actually

// later
task().then((response) => { ... })

Hoffe das hilft jemandem :)


Referenzierung Promise.all()wie in den Kommentaren gefragt (und von @Joe Frambach beantwortet), wenn ich einen Aufruf von & , 2 Funktionen vorbereiten möchte, die Versprechen zurückgebenf1('super')f2('rainbow')

const f1 = args => new Promise( ... )
const f2 = args => new Promise( ... )

const tasks = [
  () => f1('super'),
  () => f2('rainbow')
]

// later
Promise.all(tasks.map(t => t()))
  .then(resolvedValues => { ... })

Wie kann dies verwendet werden Promise.all()?
Frondor

Aufgaben = [() => f (1), () => f (2)]; Promise.all (task.map (t => t ())). Dann (...
Frambot

3

Wie wäre es mit einem umfassenderen Ansatz?

Sie könnten einen Konstruktor schreiben, der ein neues Versprechen zurückgibt, das mit .resolve()und .reject()Methoden dekoriert ist .

Sie würden sich wahrscheinlich dafür entscheiden, den Konstruktor zu benennen Deferred- ein Begriff, der in der Geschichte der Javascript-Versprechen einen hohen Stellenwert hat.

function Deferred(fn) {
    fn = fn || function(){};

    var resolve_, reject_;

    var promise = new Promise(function(resolve, reject) {
        resolve_ = resolve;
        reject_ = reject;
        fn(resolve, reject);
    });

    promise.resolve = function(val) {
        (val === undefined) ? resolve_() : resolve_(val);
        return promise;//for chainability
    }
    promise.reject = function(reason) {
        (reason === undefined) ? reject_() : reject_(reason);
        return promise;//for chainability
    }
    promise.promise = function() {
        return promise.then(); //to derive an undecorated promise (expensive but simple).
    }

    return promise;
}

Durch die Rückgabe eines dekorierten Promsie anstelle eines einfachen Objekts bleiben zusätzlich zu den Dekorationen alle natürlichen Methoden / Eigenschaften des Versprechens verfügbar.

Durch die Handhabung fnbleibt das Enthüllungsmuster auch dann verfügbar, wenn Sie es für eine verzögerte Verwendung benötigen / wählen.

DEMO

Mit dem vorhandenen Deferred()Dienstprogramm ist Ihr Code praktisch identisch mit dem jQuery-Beispiel.

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = Deferred();    
  deferreds[key] = def;
  return def.promise();
}

Ich weiß nicht, dies scheint all die Dinge wieder einzuführen, die wir vermeiden wollten, wenn wir von aufgeschobenen zu Versprechungen und dem Konstruktoransatz wechseln. Außerdem ist die Implementierung völlig zu kompliziert und enthält ein paar Fehler: a) Wenn Sie geben Deferredeinen Rückruf Parameter, sollte es wieder mit dem latenten b bezeichnet werden) Testen der value/ reasonfür undefinedabsolut unnötig c) .resolveund .rejectnie brauchen werden angekettet d) Ihre .promiseMethode gibt kein nicht dekoriertes Versprechen zurück
Bergi

Ich war schon immer unruhig beim Entwerfen von Deferreds. Ich bin mir sicher, dass sie selten sind, aber es muss Anwendungsfälle geben, in denen ein Aufschub erforderlich ist - dh wenn die Mittel zur Abrechnung zum Zeitpunkt der Erstellung eines Versprechens nicht definiert werden können. Ich verstehe (a) nicht, ich bin offen für Überzeugungsarbeit zu (b) und Sie haben sicherlich Recht mit (c).
Roamer-1888

Ich meine, du solltest es tun fn(promise)(oder besser gesagt fn(deferred)) fn(resolve, reject). Ja, ich sehe sicherlich einen Bedarf an "Resolver-Objekten", die irgendwo gespeichert und verfügbar gemacht werden können, .fulfillund .rejectMethoden, aber ich denke, diese sollten die Versprechen-Schnittstelle nicht implementieren.
Bergi

@Bergi, ich verstehe, dass jQuery zum Beispiel das Zurückgestellte selbst als Rückrufargument anzeigt. Vielleicht fehlt mir etwas, aber ich kann nicht verstehen, warum eine verzögerte Implementierung nicht die De-facto-Praxis nachahmen solltenew Promise(fn) , nur resolveund zu enthüllen reject.
Roamer-1888

Auch wenn die when.jsDokumentation von der Verwendung abrät when.defer, erkennt sie dies an in certain (rare) scenarios it can be convenient to have access to both the promise and it's associated resolving functions. Wenn diese Aussage geglaubt werden soll, sollte das, was ich oben anbiete, vernünftig erscheinen (in seltenen Szenarien).
Roamer-1888

1

Im JavaScript-Land wird es langsam besser, aber dies ist ein Fall, in dem die Dinge immer noch unnötig kompliziert sind. Hier ist ein einfacher Helfer, um die Auflösungs- und Ablehnungsfunktionen aufzudecken:

Promise.unwrapped = () => {
  let resolve, reject, promise = new Promise((_resolve, _reject) => {
    resolve = _resolve, reject = _reject
  })
  promise.resolve = resolve, promise.reject = reject
  return promise
}

// a contrived example

let p = Promise.unwrapped()
p.then(v => alert(v))
p.resolve('test')

Anscheinend gab es früher einen Promise.deferHelfer, aber selbst dieser bestand darauf, dass das zurückgestellte Objekt vom Versprechen selbst getrennt war ...

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.