Wie man ein Versprechen von setTimeout macht

90

Dies ist kein Problem der realen Welt, ich versuche nur zu verstehen, wie Versprechen geschaffen werden.

Ich muss verstehen, wie man ein Versprechen für eine Funktion macht, die nichts zurückgibt, wie setTimeout.

Angenommen, ich habe:

function async(callback){ 
    setTimeout(function(){
        callback();
    }, 5000);
}

async(function(){
    console.log('async called back');
});

Wie erstelle ich ein Versprechen, asyncdas zurückkehren kann, nachdem das setTimeoutbereit ist callback()?

Ich nahm an, dass es mich irgendwohin bringen würde:

function setTimeoutReturnPromise(){

    function promise(){}

    promise.prototype.then = function() {
        console.log('timed out');
    };

    setTimeout(function(){
        return ???
    },2000);


    return promise;
}

Aber ich kann nicht darüber hinaus denken.

Laggingreflex
quelle
Versuchen Sie, Ihre eigene Versprechensbibliothek zu erstellen ?
TJ Crowder
@TJCrowder war ich nicht, aber ich denke jetzt ist es tatsächlich das, was ich zu verstehen versuchte. So würde es eine Bibliothek machen
Laggingreflex
@ lagging: Sinnvoll, ich habe der Antwort ein Beispiel für die Implementierung eines grundlegenden Versprechens hinzugefügt.
TJ Crowder
Ich denke, dies ist ein sehr reales Problem, das ich für ein riesiges Projekt lösen musste, das mein Unternehmen baute. Es gab wahrscheinlich bessere Möglichkeiten, dies zu tun, aber ich musste die Auflösung eines Versprechens im Wesentlichen verzögern, um unseren Bluetooth-Stack zu nutzen. Ich werde unten posten, um zu zeigen, was ich getan habe.
Sunny-Mittal
1
Nur eine Anmerkung, dass im Jahr 2017 'async' ein etwas verwirrender Name für eine Funktion ist, wie Sie vielleicht habenasync function async(){...}
mikemaccana

Antworten:

118

Update (2017)

Hier sind 2017 Versprechen in JavaScript integriert, sie wurden durch die ES2015-Spezifikation hinzugefügt (Polyfills sind für veraltete Umgebungen wie IE8-IE11 verfügbar). Die Syntax verwendet einen Rückruf, den Sie an den PromiseKonstruktor (den Promise Executor ) übergeben, der die Funktionen zum Auflösen / Ablehnen des Versprechens als Argumente erhält.

Erstens, da es asyncjetzt eine Bedeutung in JavaScript hat (obwohl es in bestimmten Kontexten nur ein Schlüsselwort ist), werde ich es laterals Namen der Funktion verwenden, um Verwirrung zu vermeiden.

Grundverzögerung

Mit einheimischen Versprechungen (oder einer originalgetreuen Polyfüllung) würde es so aussehen:

function later(delay) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay);
    });
}

Beachten Sie, dass das setzt voraus , eine Version , setTimeoutdas ist kompatibel mit der Definition für Browser , wo setTimeoutkeine Argumente an die Callback übergeben wird , wenn man sich nach der Pause geben (dies ist nicht in Nicht-Browser - Umgebungen wahr sein kann, und nicht genutzt werden wahr auf Firefox, aber jetzt; es ist wahr auf Chrome und sogar zurück auf IE8).

Grundverzögerung mit Wert

Wenn Sie möchten, dass Ihre Funktion optional einen Auflösungswert in einem vage modernen Browser setTimeoutübergibt, in dem Sie nach der Verzögerung zusätzliche Argumente angeben und diese beim Aufruf an den Rückruf übergeben können, können Sie dies tun (aktuelles Firefox und Chrome; IE11 +) , vermutlich Edge; nicht IE8 oder IE9, keine Ahnung von IE10):

function later(delay, value) {
    return new Promise(function(resolve) {
        setTimeout(resolve, delay, value); // Note the order, `delay` before `value`
        /* Or for outdated browsers that don't support doing that:
        setTimeout(function() {
            resolve(value);
        }, delay);
        Or alternately:
        setTimeout(resolve.bind(null, value), delay);
        */
    });
}

Wenn Sie die Pfeilfunktionen ES2015 + verwenden, kann dies präziser sein:

function later(delay, value) {
    return new Promise(resolve => setTimeout(resolve, delay, value));
}

oder auch

const later = (delay, value) =>
    new Promise(resolve => setTimeout(resolve, delay, value));

Stornierbare Verzögerung mit Wert

Wenn Sie das Timeout stornieren möchten, können Sie nicht einfach ein Versprechen von zurückgeben later, da Versprechen nicht storniert werden können.

Wir können jedoch problemlos ein Objekt mit einer cancelMethode und einem Accessor für das Versprechen zurückgeben und das Versprechen beim Abbrechen ablehnen:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

Live-Beispiel:

const later = (delay, value) => {
    let timer = 0;
    let reject = null;
    const promise = new Promise((resolve, _reject) => {
        reject = _reject;
        timer = setTimeout(resolve, delay, value);
    });
    return {
        get promise() { return promise; },
        cancel() {
            if (timer) {
                clearTimeout(timer);
                timer = 0;
                reject();
                reject = null;
            }
        }
    };
};

const l1 = later(100, "l1");
l1.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l1 cancelled"); });

const l2 = later(200, "l2");
l2.promise
  .then(msg => { console.log(msg); })
  .catch(() => { console.log("l2 cancelled"); });
setTimeout(() => {
  l2.cancel();
}, 150);


Ursprüngliche Antwort von 2014

Normalerweise haben Sie eine Versprechensbibliothek (eine, die Sie selbst schreiben, oder eine der mehreren da draußen). Diese Bibliothek hat normalerweise ein Objekt, das Sie erstellen und später "auflösen" können, und dieses Objekt hat ein "Versprechen", das Sie von ihm erhalten können.

Dann laterwürde es ungefähr so ​​aussehen:

function later() {
    var p = new PromiseThingy();
    setTimeout(function() {
        p.resolve();
    }, 2000);

    return p.promise(); // Note we're not returning `p` directly
}

In einem Kommentar zu der Frage fragte ich:

Versuchen Sie, Ihre eigene Versprechensbibliothek zu erstellen?

und du sagtest

Ich war es nicht, aber ich denke jetzt ist es tatsächlich das, was ich zu verstehen versuchte. So würde es eine Bibliothek machen

Um dieses Verständnis zu erleichtern, finden Sie hier ein sehr einfaches Beispiel, das nicht im entferntesten Promises-A-konform ist: Live Copy

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Very basic promises</title>
</head>
<body>
  <script>
    (function() {

      // ==== Very basic promise implementation, not remotely Promises-A compliant, just a very basic example
      var PromiseThingy = (function() {

        // Internal - trigger a callback
        function triggerCallback(callback, promise) {
          try {
            callback(promise.resolvedValue);
          }
          catch (e) {
          }
        }

        // The internal promise constructor, we don't share this
        function Promise() {
          this.callbacks = [];
        }

        // Register a 'then' callback
        Promise.prototype.then = function(callback) {
          var thispromise = this;

          if (!this.resolved) {
            // Not resolved yet, remember the callback
            this.callbacks.push(callback);
          }
          else {
            // Resolved; trigger callback right away, but always async
            setTimeout(function() {
              triggerCallback(callback, thispromise);
            }, 0);
          }
          return this;
        };

        // Our public constructor for PromiseThingys
        function PromiseThingy() {
          this.p = new Promise();
        }

        // Resolve our underlying promise
        PromiseThingy.prototype.resolve = function(value) {
          var n;

          if (!this.p.resolved) {
            this.p.resolved = true;
            this.p.resolvedValue = value;
            for (n = 0; n < this.p.callbacks.length; ++n) {
              triggerCallback(this.p.callbacks[n], this.p);
            }
          }
        };

        // Get our underlying promise
        PromiseThingy.prototype.promise = function() {
          return this.p;
        };

        // Export public
        return PromiseThingy;
      })();

      // ==== Using it

      function later() {
        var p = new PromiseThingy();
        setTimeout(function() {
          p.resolve();
        }, 2000);

        return p.promise(); // Note we're not returning `p` directly
      }

      display("Start " + Date.now());
      later().then(function() {
        display("Done1 " + Date.now());
      }).then(function() {
        display("Done2 " + Date.now());
      });

      function display(msg) {
        var p = document.createElement('p');
        p.innerHTML = String(msg);
        document.body.appendChild(p);
      }
    })();
  </script>
</body>
</html>
TJ Crowder
quelle
modernjavascript.blogspot.com/2013/08/… related :)
Benjamin Gruenbaum
Ihre Antwort behandelt nicht cancelTimeout
Alexander Danilov
@AlexanderDanilov: Versprechen können nicht storniert werden. Sie könnten sicherlich eine Funktion schreiben, die ein Objekt mit einer Abbruchmethode und separat einen Accessor für das Versprechen zurückgibt, und dann das Versprechen ablehnen, wenn die Abbruchmethode aufgerufen wird ...
TJ Crowder
1
@ AlexanderDanilov: Ich habe einen hinzugefügt.
TJ Crowder