webentwicklung-frage-antwort-db.com.de

NodeJS Timeout ein Versprechen, wenn der Vorgang nicht rechtzeitig abgeschlossen wurde

Wie kann ich ein Versprechen nach einer bestimmten Zeit auszeiten lassen? Ich weiß, dass Q ein Versprechen-Timeout hat, aber ich verwende native NodeJS-Versprechen und sie haben keine .timeout-Funktion.

Fehlt mir eine oder ist sie anders verpackt?

Alternativ ist die unten beschriebene Implementierung gut, um den Speicher nicht zu saugen und tatsächlich wie erwartet zu arbeiten?

Kann ich es auch irgendwie global verpacken, damit ich es für jedes Versprechen, das ich einstelle, verwenden kann, ohne den setTimeout- und clearTimeout-Code wiederholen zu müssen?

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

Vielen Dank! 

24
AlexD

Native JavaScript-Versprechen haben keinen Timeout-Mechanismus.

Die Frage zu Ihrer Implementierung passt wahrscheinlich besser zu http://codereview.stackexchange.com , aber ein paar Anmerkungen:

  1. Sie bieten kein Mittel, um tatsächlich etwas in dem Versprechen zu tun, und

  2. Es ist nicht erforderlich, clearTimeout in Ihrem setTimeout - Rückruf anzugeben, da setTimeout einen einmaligen Zeitgeber festlegt.

  3. Da ein Versprechen nach seiner Auflösung/Ablehnung nicht mehr aufgelöst/abgelehnt werden kann, benötigen Sie diesen Scheck nicht.

Also vielleicht etwas in diese Richtung:

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

So verwendet:

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

(Oder Sie möchten vielleicht , dass es etwas komplizierter wird, siehe Update unter der Zeile unten.)

Ich bin etwas besorgt über die Tatsache, dass die Semantik etwas anders ist (nein new, wohingegen Sie new mit dem Konstruktor Promise verwenden), also könnten Sie das anpassen .

Das andere Problem ist natürlich, dass Sie die meiste Zeit keine neuen Versprechen konstruieren möchten und daher die obigen nicht verwenden können. Meistens haben Sie bereits ein Versprechen (das Ergebnis eines vorherigen then -Aufrufs usw.). Aber für Situationen, in denen Sie wirklich ein neues Versprechen konstruieren, können Sie so etwas wie das oben Genannte verwenden.

Sie können mit der Sache new umgehen, indem Sie Promise in eine Unterklasse unterteilen:

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

Verwendungszweck:

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

Live-Beispiel:

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

Beide rufen reject auf, wenn der Timer abläuft, auch wenn der Rückruf zuerst resolve oder reject aufruft. Das ist in Ordnung, der Status eines abgeschlossenen Versprechens kann nicht mehr geändert werden, wenn es einmal festgelegt wurde, und die Spezifikation definiert Aufrufe an resolve oder reject für ein Versprechen, das bereits als Nichtstun festgelegt wurde, das keinen Fehler auslöst .

Aber wenn es Sie stört, können Sie resolve und reject umbrechen. Hier ist myPromise so gemacht:

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

Sie können das auf ungefähr 18 verschiedene Arten drehen, aber das Grundkonzept ist, dass die resolve und reject, die wir an dem Versprechensvollstrecker übergeben, Wrapper sind, die den Timer löschen.

Mit werden jedoch Funktionen und zusätzliche Funktionsaufrufe erstellt, die Sie nicht benötigen. Die Spezifikation ist klar darüber, was die Auflösungsfunktionen tun, wenn das Versprechen bereits aufgelöst ist; Sie haben ziemlich früh aufgehört.

43
T.J. Crowder

Möglicherweise gibt es keine Unterstützung für ein Versprechen-Timeout.

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

Ein generischer Promise.timeout:

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

Beispiel:

    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.

Könnte ein bisschen kostspielig sein, weil Sie tatsächlich 3 Versprechen machen, anstatt zwei. Ich denke, es ist auf diese Weise klarer.

Möglicherweise möchten Sie ein Versprechen aufstellen, anstatt die Funktion für Sie erstellen zu lassen. Auf diese Weise trennen Sie Ihre Bedenken und konzentrieren sich letztendlich darauf, Ihr Versprechen gegen ein neu gebautes Versprechen zu richten, das bei x Milisekunden abgelehnt wird.

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

Wie benutzt man:

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.
15
MinusFour

Dies ist eine etwas alte Frage, aber ich stolperte darüber, als ich nachsah, wie ich ein Versprechen zeitlich abbrechen würde.
Obwohl alle Antworten großartig sind, fand ich die Verwendung von bluebird der Implementierung von Versprechungen als die einfachste Möglichkeit, Timeouts zu behandeln :

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

Wie Sie sehen, ist dies viel weniger Arbeit als die anderen vorgeschlagenen Lösungen. Ich dachte, ich werde es hier platzieren, damit die Leute es leichter finden können :) 

Übrigens bin ich auf keinen Fall am Bluebird-Projekt beteiligt, ich fand diese spezielle Lösung sehr nett.

6

Um einem bestehenden Versprechen ein Timeout hinzuzufügen, können Sie Folgendes verwenden:

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

Dann später:

await withTimeout(5000, doSomethingAsync());
5
Drew Noakes

Obwohl die Antworten hier gültig sind, sollten Sie nicht versuchen, das Rad neu zu erfinden, und verwenden Sie einfach eines der dutzenden verfügbaren Pakete für NPM für das selbstauflösende Versprechen.

Hier ist ein Beispiel von 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
K48