Подтвердить что ты не робот

NodeJS Тайм-аут обещания, если он не завершился вовремя

Как я могу отложить обещание через определенное время? Я знаю, что Q имеет тайм-аут обещания, но я использую собственный NodeJS promises, и у них нет функции .timeout.

Я пропустил один или его обернутый иначе?

В качестве альтернативы, реализована ли реализация ниже, чтобы не всасывать память, фактически работая как ожидалось?

Также я могу сделать его каким-то образом завернутым в глобальном масштабе, чтобы я мог использовать его для каждого обещания, которое я создаю, не повторяя код setTimeout и 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);
    });
}

Спасибо!

4b9b3361

Ответ 1

Нативный JavaScript promises не имеет механизма тайм-аута.

Вопрос о вашей реализации, вероятно, будет лучше подходит для http://codereview.stackexchange.com, но несколько примечаний:

  • Вы не предоставляете средства для фактического выполнения обещаний, а

  • Нет необходимости в clearTimeout в вашем обратном вызове setTimeout, так как setTimeout планирует одноразовый таймер.

  • Поскольку обещание не может быть разрешено/отклонено после его разрешения/отклонения, вам не нужна эта проверка.

Так что, возможно, что-то в этом роде:

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

Используется следующим образом:

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

(Или вы можете хотеть, чтобы это было немного сложнее, см. обновление в строке ниже.)

Я был бы немного обеспокоен тем фактом, что семантика немного отличается (нет new, тогда как вы используете new с конструктором Promise), поэтому вы можете настроить это.

Другая проблема, конечно, в том, что большую часть времени вы не хотите создавать новый promises, и поэтому не могли бы использовать вышеизложенное. В большинстве случаев у вас есть обещание уже (результат предыдущего вызова then и т.д.). Но для ситуаций, когда вы действительно строите новое обещание, вы можете использовать что-то вроде выше.

Вы можете иметь дело с темой new путем подкласса 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);
            }
        });
    }
}

Использование:

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

Живой пример:

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

Ответ 2

Хотя, возможно, нет поддержки тайм-аута обещания, вы можете участвовать в гонке promises:

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

Ответ 3

Это немного старый вопрос, но я наткнулся на это, когда я смотрел, как перерыв в обещании. Несмотря на то, что все ответы велики, я нашел bluebird реализацию Promises как самый простой способ таймауты обработки:

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

Как вы можете видеть, это намного меньше, чем другие предлагаемые решения. Я думал, что положу его сюда, чтобы облегчить людям его поиск:)

Btw Я никоим образом не участвовал в проекте bluebird, просто нашел это конкретное решение очень аккуратным.

Ответ 4

Чтобы добавить тайм-аут к любому существующему обещанию, вы можете использовать:

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

Затем позже:

await withTimeout(5000, doSomethingAsync());