web-dev-qa-db-fra.com

Comment faire une promesse de setTimeout

Ce n'est pas un problème du monde réel, j'essaie simplement de comprendre comment les promesses sont créées.

J'ai besoin de comprendre comment faire une promesse pour une fonction qui ne retourne rien, comme setTimeout.

Supposons que j'ai:

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

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

Comment créer une promesse que async peut retourner après que setTimeout soit prêt à callback()?

Je supposais que ça me mènerait quelque part:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

Mais je ne peux pas penser au-delà de cela.

70
laggingreflex

Mise à jour (2017)

Ici en 2017, les promesses sont intégrées à JavaScript, elles ont été ajoutées par la spécification ES2015 (les polyfill sont disponibles pour les environnements obsolètes tels que IE8-IE11). La syntaxe utilisée correspond au rappel que vous passez dans le constructeur Promise (le Promise exécuteur) qui reçoit les fonctions permettant de résoudre/rejeter la promesse sous forme d'arguments .

Premièrement, puisque async a maintenant une signification en JavaScript (même s’il ne s’agit que d’un mot-clé dans certains contextes), je vais utiliser later comme nom de la fonction pour éviter la confusion.

Retard de base

En utilisant des promesses indigènes (ou un polyfill fidèle), cela ressemblerait à ceci:

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

Notez que cela suppose une version de setTimeout conforme à la définition des navigateurssetTimeout ne transmet aucun argument au rappel, à moins que vous ne le leur donniez après l'intervalle. (Ce n'est peut-être pas le cas dans les environnements autres que les navigateurs, et ce n'était pas le cas auparavant avec Firefox, mais c'est le cas maintenant; c'est vrai pour Chrome et même de retour sur IE8).

Retard de base avec valeur

Si vous souhaitez que votre fonction transmette éventuellement une valeur de résolution, sur tout navigateur vaguement moderne, vous permettant de donner des arguments supplémentaires à setTimeout après le délai, puis de les transmettre au rappel lors de l'appel, vous pouvez le faire ( Firefox et Chrome actuels; IE11 +, vraisemblablement Edge; pas IE8 ou IE9, aucune idée de 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);
        */
    });
}

Si vous utilisez les fonctions de flèche ES2015 +, cela peut être plus concis:

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

ou même

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

Retard annulable avec valeur

Si vous voulez rendre possible l'annulation du délai, vous ne pouvez pas simplement renvoyer une promesse de later, car les promesses ne peuvent pas être annulées.

Mais nous pouvons facilement retourner un objet avec une méthode cancel et un accesseur pour la promesse, et rejeter la promesse sur cancel:

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;
            }
        }
    };
};

Exemple en direct:

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);

Réponse originale de 2014

Habituellement, vous aurez une bibliothèque de promesses (une que vous écrivez vous-même ou l’une des nombreuses disponibles). Cette bibliothèque aura généralement un objet que vous pourrez créer et "résoudre" plus tard, et cet objet aura une "promesse" que vous pourrez obtenir.

Alors, later aurait tendance à ressembler à ceci:

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

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

Dans un commentaire sur la question, j'ai demandé:

Essayez-vous de créer votre propre bibliothèque de promesses?

et vous avez dit

Je n'étais pas mais je suppose que c'est ce que j'essayais de comprendre. C'est comme ça qu'une bibliothèque le ferait

Pour faciliter cette compréhension, voici un exemple très très basique, qui n'est pas distant. Conforme à Promises-A: 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