如果未能及时完成,NodeJS超时承诺

时间:2015-09-08 15:06:49

标签: javascript node.js promise settimeout

如何在一定时间后超时承诺? 我知道Q有一个承诺超时,但我使用的是本机NodeJS承诺而且它们没有.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);
    });
}

谢谢!

7 个答案:

答案 0 :(得分:41)

原生JavaScript承诺没有任何超时机制。

关于您的实施的问题可能更适合http://codereview.stackexchange.com,但有几点注意事项:

  1. 您没有提供在承诺中实际执行任何操作的方法,

  2. clearTimeout回调中不需要setTimeout,因为setTimeout会安排一次性定时器。

  3. 由于承诺在解决/拒绝后无法解决/拒绝,因此您无需进行检查。

  4. 所以也许就是这样:

    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,而你使用newPromise构造函数),所以你可能会调整它

    另一个问题当然是,大多数时候,你不想构建新的承诺,因此无法使用上述内容。大多数情况下,您已经有了承诺(之前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>

    即使回叫首先调用rejectresolve,两者都会在计时器到期时调用reject。这没关系,一旦设定了承诺的已解决状态就无法更改,并且规范定义了对已经解决的承诺的resolvereject的调用,这些承诺不会引发错误。< / p>

    但如果它打扰你,你可以包裹resolvereject。这是myPromise这样做的:

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

    你可以用18种不同的方式来旋转它,但基本的概念是我们传递的resolvereject我们收到的promise执行器是清除计时器的包装器。

    但是,它会创建您不需要的函数和额外函数调用。 The spec is clear关于承诺已经解决后解析函数的作用;他们很早就退出了。

答案 1 :(得分:17)

虽然可能没有对承诺超时的支持,但你可以参加承诺:

&#13;
&#13;
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);
  });
&#13;
&#13;
&#13;

通用Promise.timeout

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

示例:

&#13;
&#13;
    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.
&#13;
&#13;
&#13;

可能有点贵,因为你实际上创造了3个承诺而不是2个。我认为这样做更清楚。

您可能希望设置一个承诺,而不是让函数为您构建它。通过这种方式,您可以将注意力分开,并最终专注于根据新建的承诺来实现您的承诺,该承诺将在x毫秒时拒绝。

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

使用方法:

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.

答案 2 :(得分:7)

这是一个稍微陈旧的问题,但当我在寻找如何延迟承诺时,我偶然发现了这一点 虽然所有答案都很棒,但我发现使用bluebird实施Promises是handling 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/ });

正如您所看到的,这比其他提议的解决方案少得多。我想我会把它放在这里让人们更容易找到它:)

顺便说一下,我并不参与蓝鸟项目,只是发现这个特殊的解决方案非常整洁。

答案 3 :(得分:7)

要为任何现有承诺添加超时,您可以使用:

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

答案 4 :(得分:1)

如果将代码放置在类中,则可以使用装饰器。您在utils-decoratorsnpm install --save utils-decorators)中有这样的装饰器:

import {timeout} from 'utils-decorators';

class SomeService {

   @timeout(3000)
   doSomeAsync(): Promise<any> {
    ....
   }
}

https://github.com/vlio20/utils-decorators#timeout-method

答案 5 :(得分:0)

虽然这里的答案是正确的,但您不应尝试重新发明轮子,而应使用NPM上数十种可用软件包中的一种来实现自我解决。

这里是一个example from 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()
});

答案 6 :(得分:0)

在这种情况下,包装器会很方便

用法

const result = await withTimeout(() => doSomethingAsync(...args), 3000)();

const result = await withTimeout(doSomethingAsync, 3000)(...args);

甚至

const doSomethingAsyncWithTimeout = withTimeout(doSomethingAsync, 3000);
const result = await doSomethingAsyncWithTimeout(...args);

实现

/**
 * returns a new function which calls the input function and "races" the result against a promise that throws an error on timeout.
 *
 * the result is:
 * - if your async fn takes longer than timeout ms, then an error will be thrown
 * - if your async fn executes faster than timeout ms, you'll get the normal response of the fn
 *
 * ### usage
 * ```ts
 * const result = await withTimeout(() => doSomethingAsync(...args), 3000);
 * ```
 * or
 * ```ts
 * const result = await withTimeout(doSomethingAsync, 3000)(...args);
 * ```
 * or even
 * ```ts
 * const doSomethingAsyncWithTimeout = withTimeout(doSomethingAsync, 3000);
 * const result = await doSomethingAsyncWithTimeout(...args);
 * ```
 */
const withTimeout = <R, P extends any, T extends (...args: P[]) => Promise<R>>(logic: T, ms: number) => {
  return (...args: Parameters<T>) => {
    // create a promise that rejects in <ms> milliseconds; https://italonascimento.github.io/applying-a-timeout-to-your-promises/
    const timeout = new Promise((resolve, reject) => {
      const id = setTimeout(() => {
        clearTimeout(id);
        reject(new Error(`promise was timed out in ${ms} ms, by withTimeout`));
      }, ms); // tslint:disable-line align
    });

    // returns a "race" between our timeout and the function executed with the input params
    return Promise.race([
      logic(...args), // the wrapped fn, executed w/ the input params
      timeout, // the timeout
    ]) as Promise<R>;
  };
};