javascript - then - NodeJS Timeout a Promise si no se completó a tiempo
promise timeout (5)
Esta es una pregunta un poco vieja, pero tropecé con esto cuando estaba buscando cómo cumplir una promesa.
Si bien todas las respuestas son excelentes, descubrí que usar la implementación de Promises de bluebird es la forma más fácil de manejar los tiempos de espera :
var Promise = require(''bluebird'');
var p = new Promise(function(reject, resolve) { /.../ });
p.timeout(3000) //make the promise timeout after 3000 milliseconds
.then(function(data) { /handle resolved promise/ })
.catch(Promise.TimeoutError, function(error) { /handle timeout error/ })
.catch(function(error) { /handle any other non-timeout errors/ });
Como puede ver, esto es mucho menos trabajo que las otras soluciones propuestas. Pensé que lo pondría aquí para que sea más fácil para las personas encontrarlo :)
Por cierto, no estoy involucrado de ninguna manera en el proyecto Bluebird, solo encontré esta solución en particular muy clara.
¿Cómo puedo caducar una promesa después de cierto tiempo? Sé que Q tiene un tiempo de espera de promesa, pero estoy usando promesas nativas de NodeJS y no tienen la función de tiempo de espera.
¿Me falta uno o está envuelto de manera diferente?
Alternativamente, ¿la implementación que se muestra a continuación es buena para no absorber la memoria y realmente funciona como se esperaba?
¿También puedo hacerlo de alguna manera envuelto globalmente para poder usarlo para cada promesa que creo, sin tener que repetir el código setTimeout y clearTimeout?
function run() {
logger.info(''DoNothingController working on process id {0}...''.format(process.pid));
myPromise(4000)
.then(function() {
logger.info(''Successful!'');
})
.catch(function(error) {
logger.error(''Failed! '' + error);
});
}
function myPromise(ms) {
return new Promise(function(resolve, reject) {
var hasValueReturned;
var promiseTimeout = setTimeout(function() {
if (!hasValueReturned) {
reject(''Promise timed out after '' + ms + '' ms'');
}
}, ms);
// Do something, for example for testing purposes
setTimeout(function() {
resolve();
clearTimeout(promiseTimeout);
}, ms - 2000);
});
}
¡Gracias!
Las promesas nativas de JavaScript no tienen ningún mecanismo de tiempo de espera.
La pregunta sobre su implementación probablemente sería mejor para http://codereview.stackexchange.com , pero un par de notas:
Usted no proporciona un medio para hacer realmente nada en la promesa, y
No hay necesidad de
clearTimeout
dentro de susetTimeout
llamada desetTimeout
, ya quesetTimeout
programa un temporizador único.Dado que una promesa no se puede resolver / rechazar una vez que se haya resuelto / rechazado, no necesita esa verificación.
Así que tal vez algo a lo largo de estas líneas:
function myPromise(ms, callback) {
return new Promise(function(resolve, reject) {
// Set up the real work
callback(resolve, reject);
// Set up the timeout
setTimeout(function() {
reject(''Promise timed out after '' + ms + '' ms'');
}, ms);
});
}
Utilizado de esta manera:
myPromise(2000, function(resolve, reject) {
// Real work is here
});
(O quizás desee que sea un poco más complicado, consulte la actualización debajo de la línea a continuación).
Me preocuparía un poco el hecho de que la semántica sea un poco diferente (no es new
, mientras que usted usa new
con el constructor Promise
), por lo que podría ajustar eso.
El otro problema, por supuesto, es que la mayoría de las veces, no desea construir nuevas promesas y, por lo tanto, no puede usar las anteriores. La mayoría de las veces, ya tiene una promesa (el resultado de una llamada anterior, etc.). Pero para situaciones en las que realmente estás construyendo una nueva promesa, podrías usar algo como lo anterior.
Puedes lidiar con lo new
mediante la subclasificación de Promise
:
class MyPromise extends Promise {
constructor(ms, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
// This code to do it is ugly, could use some love, but it
// gives you the idea.
let haveTimeout = typeof ms === "number" && typeof callback === "function";
let init = haveTimeout ? callback : ms;
super((resolve, reject) => {
init(resolve, reject);
if (haveTimeout) {
setTimeout(() => {
reject("Timed out");
}, ms);
}
});
}
}
Uso:
let p = new MyPromise(300, function(resolve, reject) {
// ...
});
p.then(result => {
})
.catch(error => {
});
Ejemplo vivo:
// Uses var instead of let and non-arrow functions to try to be
// compatible with browsers that aren''t quite fully ES6 yet, but
// do have promises...
(function() {
"use strict";
class MyPromise extends Promise {
constructor(ms, callback) {
var haveTimeout = typeof ms === "number" && typeof callback === "function";
var init = haveTimeout ? callback : ms;
super(function(resolve, reject) {
init(resolve, reject);
if (haveTimeout) {
setTimeout(function() {
reject("Timed out");
}, ms);
}
});
}
}
var p = new MyPromise(100, function(resolve, reject) {
// We never resolve/reject, so we test the timeout
});
p.then(function(result) {
snippet.log("Resolved: " + result);
}).catch(function(reject) {
snippet.log("Rejected: " + reject);
});
})();
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Ambos llamarán al reject
cuando el temporizador expire, incluso si las llamadas de devolución de llamada se resolve
o reject
primero. Eso está bien, el estado de liquidación de una promesa no se puede cambiar una vez que se establece, y la especificación define las llamadas a resolve
o reject
en una promesa que ya está resuelta como do-nothings que no generan un error.
Pero si te molesta, puedes envolver resolve
y reject
. Aquí está myPromise
hecho de esa manera:
function myPromise(ms, callback) {
return new Promise(function(resolve, reject) {
// Set up the timeout
let timer = setTimeout(function() {
reject(''Promise timed out after '' + ms + '' ms'');
}, ms);
let cancelTimer = _ => {
if (timer) {
clearTimeout(timer);
timer = 0;
}
};
// Set up the real work
callback(
value => {
cancelTimer();
resolve(value);
},
error => {
cancelTimer();
reject(error);
}
);
});
}
Puede girar eso de aproximadamente 18 formas diferentes, pero el concepto básico es que la resolve
y el reject
que aprobamos al ejecutor de la promesa que recibimos son envoltorios que eliminan el temporizador.
Pero , eso crea funciones y llamadas a funciones extra que no necesita. La especificación es clara acerca de lo que hacen las funciones de resolución cuando la promesa ya está resuelta; renunciaron bastante temprano.
Para agregar un tiempo de espera a cualquier promesa existente, puede utilizar:
const withTimeout = (millis, promise) => {
const timeout = new Promise((resolve, reject) =>
setTimeout(
() => reject(`Timed out after ${millis} ms.`),
millis));
return Promise.race([
promise,
timeout
]);
};
Entonces despúes:
await withTimeout(5000, doSomethingAsync());
Si bien las respuestas aquí son válidas, no debe intentar reinventar la rueda y simplemente usar uno de los docenas de paquetes disponibles en NPM para la promesa de resolución automática.
Aquí hay un ejemplo de NPM :
const { TimeoutResolvePromise, TimeoutRejectPromise } = require(''nodejs-promise-timeout'');
const TIMEOUT_DELAY = 2000;
// This promise will reject after 2 seconds:
let promise1 = new TimeoutRejectPromise(TIMEOUT_DELAY, (resolve, reject) => {
// Do something useful here, then call resolve() or reject()
});
Si bien tal vez no hay soporte para un tiempo de espera de promesa, podría prometer una carrera:
var race = Promise.race([
new Promise(function(resolve){
setTimeout(function() { resolve(''I did it''); }, 1000);
}),
new Promise(function(resolve, reject){
setTimeout(function() { reject(''Timed out''); }, 800);
})
]);
race.then(function(data){
console.log(data);
}).catch(function(e){
console.log(e);
});
Un Promise.timeout
genérico:
Promise.timeout = function(timeout, cb){
return Promise.race([
new Promise(cb),
new Promise(function(resolve, reject){
setTimeout(function() { reject(''Timed out''); }, timeout);
})
]);
}
Ejemplo:
Promise.timeout = function(timeout, cb) {
return Promise.race([
new Promise(cb),
new Promise(function(resolve, reject) {
setTimeout(function() {
reject(''Timed out'');
}, timeout);
})
]);
}
function delayedHello(cb){
setTimeout(function(){
cb(''Hello'');
}, 1000);
}
Promise.timeout(800, delayedHello).then(function(data){
console.log(data);
}).catch(function(e){
console.log(e);
}); //delayedHello doesn''t make it.
Promise.timeout(1200, delayedHello).then(function(data){
console.log(data);
}).catch(function(e){
console.log(e);
}); //delayedHello makes it.
Podría ser un poco costoso, porque en realidad estás creando 3 promesas en lugar de 2. Aunque creo que está más claro de esta manera.
Es posible que desee configurar una promesa en lugar de que la función la construya por usted. De esta manera, separa las preocupaciones y finalmente se enfoca en competir con su promesa contra una promesa recién creada que se rechazará en x
milisegundos.
Promise.timeout = function(timeout, promise){
return Promise.race([
promise,
new Promise(function(resolve, reject){
setTimeout(function() { reject(''Timed out''); }, timeout);
})
]);
}
Cómo utilizar:
var p = new Promise(function(resolve, reject){
setTimeout(function() { resolve(''Hello''); }, 1000);
});
Promise.timeout(800, p); //will be rejected, as the promise takes at least 1 sec.