NodeJS Délai d'attente d'une Promesse si n'a pas pu terminer dans le temps

Comment puis-je timeout une promesse après un certain temps? Je sais que Q A un temps mort de promesse, mais j'utilise les promesses natives de NodeJS et ils ne l'ont pas .fonction de délai.

Est-ce que je manque un ou son enveloppé différemment?

sinon, l'implémentation ci-dessous est-elle bonne pour ne pas aspirer la mémoire, et fonctionne-t-elle comme prévu?

je peux aussi le faire d'une manière ou d'une autre enveloppé globalement pour que je puisse l'utiliser pour chaque promesse que je crée, sans avoir à répéter le code setTimeout et 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);
    });
}

Merci!

23
demandé sur AlexD 2015-09-08 18:06:48

5 réponses

les promesses JavaScript natives n'ont pas de mécanisme de temporisation.

La question à propos de votre mise en œuvre serait probablement un meilleur ajustement pour http://codereview.stackexchange.com, mais quelques remarques:

  1. vous ne fournissez pas un moyen de réellement faire quoi que ce soit dans la promesse, et

  2. Il n'y a pas besoin de clearTimeout dans votre setTimeout rappel, depuis setTimeout horaires un one-off minuterie.

  3. Puisqu'une promesse ne peut pas être résolue/rejetée une fois qu'elle a été résolue/rejetée, vous n'avez pas besoin de cette vérification.

alors peut-être quelque chose dans ce sens:

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

utilise comme ceci:

myPromise(2000, function(resolve, reject) {
    // Real work is here
});

(Ou voulez qu'il soit un peu plus compliqué, voir mise à jour sous la ligne ci-dessous.)

je serais légèrement préoccupé par le fait que la sémantique sont légèrement différents (Non new, alors que vous ne l'utilisez newPromise constructeur), donc vous pouvez ajuster cela.

l'autre problème, bien sûr, est que la plupart du temps, vous ne voulez pas construire de nouvelles promesses, et donc ne pouvait pas utiliser ce qui précède. La plupart du temps, vous avez déjà une promesse (le résultat d'un précédent then appel, etc.). Mais pour les situations où vous construisez vraiment une nouvelle promesse, vous pourriez utiliser quelque chose comme ce qui précède.

Vous peut faire face à la new chose subclassing 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);
            }
        });
    }
}

Utilisation:

let p = new MyPromise(300, function(resolve, reject) {
    // ...
});
p.then(result => {
})
.catch(error => {
});

Live Example:

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

les deux appelleront reject quand le timer expire même si le callback appelle resolve ou reject en premier. C'est très bien, l'état d'une promesse ne peut pas être modifié une fois qu'elle est définie, et le spec définit les appels à resolve ou reject sur une promesse de déjà réglé comme un rien qui ne soulève pas d'erreur.

Mais si cela vous dérange, vous pouvez envelopper resolve et reject. Ici myPromise fait de cette manière:

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

vous pouvez le faire de 18 façons différentes, mais le concept de base est que le resolve et reject nous transmettons l'exécuteur de la promesse que nous recevons sont des enveloppes qui libèrent le minuteur.

Mais, qui crée des fonctions et des appels de fonctions supplémentaires dont vous n'avez pas besoin. La spec est clair sur ce que les fonctions de résolution font lorsque la promesse est déjà résolue; ils quittent assez tôt.

35
répondu T.J. Crowder 2017-04-27 06:59:17

alors que peut-être il n'y a aucun soutien pour un temps d'arrêt de promesse, vous pourriez course promesses:

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

générique Promise.timeout:

Promise.timeout = function(timeout, cb){
  return Promise.race([
  new Promise(cb),
  new Promise(function(resolve, reject){
    setTimeout(function() { reject('Timed out'); }, timeout);
  })
]);
}

Exemple:

    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.

pourrait être un peu coûteux, parce que vous créez en fait 3 promesses au lieu de 2. Je pense que c'est plus clair de cette façon.

vous pourriez vouloir configurer une promesse au lieu d'avoir la fonction la construire pour vous. De cette façon, vous séparer préoccupations et vous êtes finalement concentré sur la course de votre promesse contre une promesse nouvellement construit qui rejettera à x millisecondes.

Promise.timeout = function(timeout, promise){
  return Promise.race([
  promise,
  new Promise(function(resolve, reject){
    setTimeout(function() { reject('Timed out'); }, timeout);
  })
]);
}

utilisation:

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.
14
répondu MinusFour 2015-09-08 21:03:05

c'est une question un peu vieille, mais je suis tombé sur cela quand je regardais comment timeout une promesse.

Alors que toutes les réponses sont bonnes, j'ai trouvé en utilisant merle-bleu la mise en oeuvre des promesses comme moyen le plus facile de gestion des timeouts:

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

Comme vous pouvez le voir c'est donc beaucoup moins de travail que les autres solutions proposées. J'ai pensé que je vais le mettre ici pour le rendre plus facile pour les gens à trouver :)

Btw, je ne suis pas en toute moyens impliqués dans le projet bluebird, vient de trouver cette solution particulière très soignée.

6
répondu Daniel Gruszczyk 2015-12-18 12:10:35

Pour ajouter un délai à toute promesse, vous pouvez utiliser:

const withTimeout = (millis, promise) => {
    const timeout = new Promise((resolve, reject) =>
        setTimeout(
            () => reject(`Timed out after ${millis} ms.`),
            millis));
    return Promise.race([
        promise,
        timeout
    ]);
};

Puis, plus tard:

await withTimeout(5000, doSomethingAsync());
4
répondu Drew Noakes 2017-10-10 20:18:51

alors que les réponses ici sont valables, vous ne devriez pas essayer de réinventer la roue et utiliser un des paquets disponibles sur NPM pour la promesse auto-résolutive.

Voici un exemple 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()
});
0
répondu K48 2018-07-05 03:42:38