web-dev-qa-db-de.com

So machen Sie ein Versprechen von setTimeout

Dies ist kein reales Problem, ich versuche nur zu verstehen, wie Versprechen entstehen.

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, das async zurückgeben kann, nachdem setTimeout für callback() bereit ist?

Ich nahm an, dass das Einwickeln irgendwohin führen würde:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

Aber darüber hinaus kann ich nicht nachdenken.

70
laggingreflex

Update (2017)

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

Erstens, da async jetzt eine Bedeutung in JavaScript hat (obwohl es in bestimmten Kontexten nur ein Schlüsselwort ist), werde ich later als Namen von verwenden die Funktion, um Verwirrung zu vermeiden.

Grundlegende Verzögerung

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

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

Beachten Sie, dass dies eine Version von setTimeout voraussetzt, die mit der Definition für Browser kompatibel ist, wobei setTimeout keine Argumente an den Rückruf übergibt, es sei denn, Sie geben sie nach dem Intervall an (Dies mag in Nicht-Browser-Umgebungen nicht zutreffen und war in Firefox früher nicht zutreffend, ist es jetzt aber; es gilt für Chrome und sogar für IE8).

Grundlegende Verzögerung mit Wert

Wenn Sie möchten, dass Ihre Funktion optional einen Auflösungswert übergibt, können Sie dies in einem vage modernen Browser tun, der es Ihnen ermöglicht, setTimeout nach der Verzögerung zusätzliche Argumente zu übergeben und diese dann beim Aufruf an den Rückruf weiterzuleiten ( aktueller 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 Pfeilfunktionen für 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));

Annullierbare Verzögerung mit Wert

Wenn Sie das Abbrechen des Timeouts ermöglichen möchten, können Sie nicht einfach ein Versprechen von later zurückgeben, da Versprechen nicht storniert werden können.

Wir können jedoch problemlos ein Objekt mit einer cancel -Methode 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 geschrieben haben, oder eine der mehreren, die es gibt). Diese Bibliothek enthält normalerweise ein Objekt, das Sie erstellen und später "auflösen" können, und dieses Objekt enthält ein "Versprechen", das Sie von ihm erhalten können.

Dann würde later 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 Versprechen-Bibliothek zu erstellen?

und du sagtest

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

Um dieses Verständnis zu erleichtern, ist hier ein sehr, sehr einfach Beispiel, das nicht mit Promises-A kompatibel 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>
83
T.J. Crowder