Cómo hacer una promesa desde setTimeout

96

Este no es un problema del mundo real, solo estoy tratando de entender cómo se crean las promesas.

Necesito entender cómo hacer una promesa para una función que no devuelve nada, como setTimeout.

Supongamos que tengo:

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

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

¿Cómo creo una promesa que asyncpueda volver después de que setTimeoutesté lista callback()?

Supuse que envolverlo me llevaría a algún lado:

function setTimeoutReturnPromise(){

    function promise(){}

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

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


    return promise;
}

Pero no puedo pensar más allá de esto.

reflejo
fuente
¿Está intentando crear su propia biblioteca de promesas?
TJ Crowder
@TJCrowder No lo estaba, pero supongo que ahora eso es lo que estaba tratando de entender. Así es como lo haría una biblioteca
laggingreflex
@ rezagado: Tiene sentido, he agregado un ejemplo de implementación de promesa básica a la respuesta.
TJ Crowder
Creo que este es un problema del mundo real y que tuve que resolver para un proyecto masivo que mi empresa estaba construyendo. Probablemente había mejores formas de hacerlo, pero esencialmente necesitaba retrasar la resolución de una promesa por el bien de nuestra pila de bluetooth. Publicaré a continuación para mostrar lo que hice.
sunny-mittal
1
Solo una nota que en 2017 'async' es un nombre algo confuso para una función, como podría haberlo hechoasync function async(){...}
mikemaccana

Respuestas:

132

Actualización (2017)

Aquí, en 2017, las promesas están integradas en JavaScript, fueron agregadas por la especificación ES2015 (los polyfills están disponibles para entornos obsoletos como IE8-IE11). La sintaxis que utilizaron usa una devolución de llamada que se pasa al Promiseconstructor (el Promise ejecutor ) que recibe las funciones para resolver / rechazar la promesa como argumentos.

Primero, dado que asyncahora tiene un significado en JavaScript (aunque es solo una palabra clave en ciertos contextos), voy a usarlater como nombre de la función para evitar confusiones.

Retraso básico

Usando promesas nativas (o un polyfill fiel) se vería así:

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

Tenga en cuenta que eso supone una versión setTimeoutcompatible con la definición de navegadores dondesetTimeout no pasa ningún argumento a la devolución de llamada a menos que los proporcione después del intervalo (esto puede no ser cierto en entornos que no son de navegador, y no solía ser así cierto en Firefox, pero lo es ahora; es cierto en Chrome e incluso en IE8).

Retraso básico con valor

Si desea que su función pase opcionalmente un valor de resolución, en cualquier navegador vagamente moderno que le permita dar argumentos adicionales setTimeoutdespués de la demora y luego pasarlos a la devolución de llamada cuando se llame, puede hacerlo (Firefox y Chrome actuales; IE11 + , presumiblemente Edge; no IE8 o IE9, no tengo idea 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 está utilizando las funciones de flecha ES2015 +, eso puede ser más conciso:

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

o incluso

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

Retraso cancelable con valor

Si desea que sea posible cancelar el tiempo de espera, no puede simplemente devolver una promesa de later, porque las promesas no se pueden cancelar.

Pero podemos devolver fácilmente un objeto con un cancelmétodo y un descriptor de acceso para la promesa, y rechazar la promesa al cancelar:

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

Ejemplo en vivo:

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


Respuesta original de 2014

Por lo general, tendrá una biblioteca de promesas (una que escriba usted mismo o una de las varias que hay). Esa biblioteca generalmente tendrá un objeto que puede crear y luego "resolver", y ese objeto tendrá una "promesa" que puede obtener de él.

Entonces latertendería a verse algo como esto:

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

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

En un comentario sobre la pregunta, pregunté:

¿Está intentando crear su propia biblioteca de promesas?

y tú dijiste

No lo estaba, pero supongo que ahora eso es realmente lo que estaba tratando de entender. Así como lo haría una biblioteca

Para ayudar a esa comprensión, aquí hay un ejemplo muy básico , que no cumple remotamente con 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
fuente
modernjavascript.blogspot.com/2013/08/… relacionado :)
Benjamin Gruenbaum
su respuesta no maneja cancelTimeout
Alexander Danilov
@AlexanderDanilov: Las promesas no se pueden cancelar. Ciertamente, podría escribir una función que devolviera un objeto con un método de cancelación y, por separado, un descriptor de acceso para la promesa, luego rechazar la promesa si se llamara al método de cancelación ...
TJ Crowder
1
@AlexanderDanilov: Seguí adelante y agregué uno.
TJ Crowder
1
const setTimeoutAsync = (cb, delay) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(cb());
    }, delay);
  });

Podemos pasar 'cb fxn' personalizado como este 👆🏽

CodeFinity
fuente
0

Esta no es una respuesta a la pregunta original. Pero, como una pregunta original no es un problema del mundo real, no debería ser un problema. Traté de explicarle a un amigo qué son las promesas en JavaScript y la diferencia entre promesa y devolución de llamada.

El siguiente código sirve como explicación:

//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
fuente