Comment faire une promesse depuis setTimeout

91

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 renvoie 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 qui asyncpeut revenir une fois que le setTimeoutest prêt callback()?

Je supposais que l'envelopper m'emmè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 ça.

réflexe de retard
la source
Essayez-vous de créer votre propre bibliothèque de promesses?
TJ Crowder
@TJCrowder Je n'étais pas mais je suppose que c'est en fait ce que j'essayais de comprendre. Voilà comment une bibliothèque le ferait
laggingreflex
@ retard: Cela a du sens, j'ai ajouté un exemple d'implémentation de base de promesse à la réponse.
TJ Crowder
Je pense que c'est un problème très réel et que j'ai dû résoudre pour un énorme projet que mon entreprise était en train de construire. Il y avait probablement de meilleures façons de le faire, mais j'avais essentiellement besoin de retarder la résolution d'une promesse pour le bien de notre pile Bluetooth. Je posterai ci-dessous pour montrer ce que j'ai fait.
sunny-mittal
1
Juste une note qu'en 2017 `` async '' est un nom quelque peu déroutant pour une fonction, comme vous pourriez l'avoirasync function async(){...}
mikemaccana

Réponses:

121

Mise à jour (2017)

Ici en 2017, les promesses sont intégrées à JavaScript, elles ont été ajoutées par la spécification ES2015 (les polyfills sont disponibles pour les environnements obsolètes comme IE8-IE11). La syntaxe avec laquelle ils sont allés utilise un rappel que vous passez au Promiseconstructeur (l' Promise exécuteur ) qui reçoit les fonctions de résolution / rejet de la promesse comme arguments.

Premièrement, puisque asyncmaintenant a une signification en JavaScript (même si ce n'est qu'un mot-clé dans certains contextes), je vais utiliser latercomme nom de la fonction pour éviter toute confusion.

Délai de base

En utilisant des promesses natives (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 setTimeoutqui est conforme à la définition des navigateurssetTimeoutne transmet aucun argument au rappel à moins que vous ne les donniez après l'intervalle (cela peut ne pas être vrai dans les environnements sans navigateur, et ce n'était pas le cas auparavant vrai sur Firefox, mais c'est maintenant; c'est vrai sur Chrome et même de retour sur IE8).

Délai de base avec valeur

Si vous voulez que votre fonction passe éventuellement une valeur de résolution, sur n'importe quel navigateur vaguement moderne qui vous permet de donner des arguments supplémentaires setTimeoutaprès le délai, puis de les transmettre au rappel lorsqu'il est appelé, vous pouvez le faire (Firefox et Chrome actuels; IE11 + , probablement 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 fléchées 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 souhaitez rendre possible l'annulation du délai d'expiration, vous ne pouvez pas simplement renvoyer une promesse later, car les promesses ne peuvent pas être annulées.

Mais nous pouvons facilement renvoyer un objet avec une cancelméthode et un accesseur pour la promesse, et rejeter la promesse en cas d'annulation:

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 (celle que vous écrivez vous-même, ou l'une des nombreuses). 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 en tirer.

Alors lateraurait tendance à ressembler à quelque chose comme 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 tu as dit

Je ne l'étais pas mais je suppose que c'est en fait ce que j'essayais de comprendre. Voilà comment une bibliothèque le ferait

Pour aider à cette compréhension, voici un exemple très très basique , qui n'est pas conforme à distance 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>
TJ Crowder
la source
modernjavascript.blogspot.com/2013/08/… lié :)
Benjamin Gruenbaum
votre réponse ne gère pas cancelTimeout
Alexander Danilov
@AlexanderDanilov: Les promesses ne sont pas annulables. Vous pourriez certainement écrire une fonction qui retournait un objet avec une méthode d'annulation et, séparément, un accesseur pour la promesse, puis rejeter la promesse si la méthode d'annulation était appelée ...
TJ Crowder
1
@AlexanderDanilov: Je suis allé de l'avant et en ai ajouté un.
TJ Crowder
0

Ce n'est pas une réponse à la question initiale. Mais, comme une question originale n'est pas un problème réel, elle ne devrait pas être un problème. J'ai essayé d'expliquer à un ami ce que sont les promesses en JavaScript et la différence entre promesse et rappel.

Le code ci-dessous sert d'explication:

//very basic callback example using setTimeout
//function a is asynchronous function
//function b used as a callback
function a (callback){
    setTimeout (function(){
       console.log ('using callback:'); 
       let mockResponseData = '{"data": "something for callback"}'; 
       if (callback){
          callback (mockResponseData);
       }
    }, 2000);

} 

function b (dataJson) {
   let dataObject = JSON.parse (dataJson);
   console.log (dataObject.data);   
}

a (b);

//rewriting above code using Promise
//function c is asynchronous function
function c () {
   return new Promise(function (resolve, reject) {
     setTimeout (function(){
       console.log ('using promise:'); 
       let mockResponseData = '{"data": "something for promise"}'; 
       resolve(mockResponseData); 
    }, 2000);      
   }); 

}

c().then (b);

JsFiddle

yurin
la source