如果NodeJS没有及时完成,它将返回Promise

h22fl7wq  于 12个月前  发布在  Node.js
关注(0)|答案(8)|浏览(184)

我知道Q有一个promise timeout,但我使用的是原生NodeJS promise,它们没有.timeout函数。
是我漏了一个还是它的 Package 不同?
或者,下面的实现在不占用内存的情况下是否很好,实际上是否按预期工作?
另外,我可以让它以某种方式全局 Package ,这样我就可以在我创建的每个promise中使用它,而不必重复setlog和clearlog代码?

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

字符串
谢谢你,谢谢

col17t5w

col17t5w1#

原生JavaScript promise没有任何超时机制。
关于您的实现的问题可能更适合http://codereview.stackexchange.com,但有几个注意事项:
1.在promise中,你没有提供一种实际做任何事情的方法,
1.在setTimeout回调中不需要clearTimeout,因为setTimeout调度一个一次性计时器。
1.因为一旦promise被resolved/rejected,它就不能被resolved/rejected,所以你不需要检查。
所以继续你的myPromise函数方法,可能是这样的:

function myPromise(timeout, callback) {
    return new Promise((resolve, reject) => {
        // Set up the timeout
        const timer = setTimeout(() => {
            reject(new Error(`Promise timed out after ${timeout} ms`));
        }, timeout);

        // Set up the real work
        callback(
            (value) => {
                clearTimeout(timer);
                resolve(value);
            },
            (error) => {
                clearTimeout(timer);
                reject(error);
            }
        );
    });
}

字符串
使用方式如下:

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


(Or可能稍微不那么复杂,请参阅下面的分隔符。)
我有点担心语义不同的事实(没有new,而你确实使用Promise构造函数的new),但更大的问题是,它假设你总是从头开始创建一个promise,但你通常希望能够使用你已经拥有的promise。
您可以通过子类化Promise来处理这两个问题:

class MyPromise extends Promise {
    constructor(timeout, 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.
        const haveTimeout = typeof timeout === "number";
        const init = haveTimeout ? callback : timeout;
        super((resolve, reject) => {
            if (haveTimeout) {
                const timer = setTimeout(() => {
                    reject(new Error(`Promise timed out after ${timeout}ms`));
                }, timeout);
                init(
                    (value) => {
                        clearTimeout(timer);
                        resolve(value);
                    },
                    (error) => {
                        clearTimeout(timer);
                        reject(error);
                    }
                );
            } else {
                init(resolve, reject);
            }
        });
    }
    // Pick your own name of course. (You could even override `resolve` itself
    // if you liked; just be sure to do the same arguments detection we do
    // above in the constructor, since you need to support the standard use of
    // `resolve`.)
    static resolveWithTimeout(timeout, x) {
        if (!x || typeof x.then !== "function") {
            // `x` isn't a thenable, no need for the timeout,
            // fulfill immediately
            return this.resolve(x);
        }
        return new this(timeout, x.then.bind(x));
    }
}


用法(如果构造一个新的promise):

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


用法(如果使用你已经拥有的promise):

MyPromise.resolveWithTimeout(100, somePromiseYouAlreadyHave)
.then((value) => {
    // ...
})
.catch((error) => {
    // ...
});

示例:

"use strict";
    
class MyPromise extends Promise {
    constructor(timeout, 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.
        const haveTimeout = typeof timeout === "number";
        const init = haveTimeout ? callback : timeout;
        super((resolve, reject) => {
            if (haveTimeout) {
                const timer = setTimeout(() => {
                    reject(new Error(`Promise timed out after ${timeout}ms`));
                }, timeout);
                init(
                    (value) => {
                        clearTimeout(timer);
                        resolve(value);
                    },
                    (error) => {
                        clearTimeout(timer);
                        reject(error);
                    }
                );
            } else {
                init(resolve, reject);
            }
        });
    }
    // Pick your own name of course. (You could even override `resolve` itself
    // if you liked; just be sure to do the same arguments detection we do
    // above in the constructor, since you need to support the standard use of
    // `resolve`.)
    static resolveWithTimeout(timeout, x) {
        if (!x || typeof x.then !== "function") {
            // `x` isn't a thenable, no need for the timeout,
            // fulfill immediately
            return this.resolve(x);
        }
        return new this(timeout, x.then.bind(x));
    }
}

// Some functions for the demonstration
const neverSettle = () => new Promise(() => {});
const fulfillAfterDelay = (delay, value) => new Promise((resolve) => setTimeout(resolve, delay, value));
const rejectAfterDelay = (delay, error) => new Promise((resolve, reject) => setTimeout(reject, delay, error));

const examples = [
    function usageWhenCreatingNewPromise1() {
        console.log("Showing timeout when creating new promise");
        const p = new MyPromise(100, (resolve, reject) => {
            // We never resolve/reject, so we test the timeout
        });
        return p.then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    function usageWhenCreatingNewPromise2() {
        console.log("Showing when the promise is fulfilled before the timeout");
        const p = new MyPromise(100, (resolve, reject) => {
            setTimeout(resolve, 50, "worked");
        });
        return p.then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    function usageWhenCreatingNewPromise3() {
        console.log("Showing when the promise is rejected before the timeout");
        const p = new MyPromise(100, (resolve, reject) => {
            setTimeout(reject, 50, new Error("failed"));
        });
        return p.then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    function usageWhenYouAlreadyHaveAPromise1() {
        console.log("Showing timeout when using a promise we already have");
        return MyPromise.resolveWithTimeout(100, neverSettle())
        .then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    function usageWhenYouAlreadyHaveAPromise2() {
        console.log("Showing fulfillment when using a promise we already have");
        return MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"))
        .then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    function usageWhenYouAlreadyHaveAPromise3() {
        console.log("Showing rejection when using a promise we already have");
        return MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")))
        .then((value) => {
            console.log(`Fulfilled: ${value}`);
        })
        .catch((error) => {
            console.log(`Rejected: ${error}`);
        });
    },

    async function usageInAnAsyncFunction1() {
        console.log("Showing timeout in async function");
        try {
            const value = await MyPromise.resolveWithTimeout(100, neverSettle());
            console.log(`Fulfilled: ${value}`);
        } catch (error) {
            console.log(`Rejected: ${error}`);
        }
    },

    async function usageInAnAsyncFunction2() {
        console.log("Showing fulfillment in async function");
        try {
            const value = await MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"));
            console.log(`Fulfilled: ${value}`);
        } catch (error) {
            console.log(`Rejected: ${error}`);
        }
    },

    async function usageInAnAsyncFunction3() {
        console.log("Showing rejection in async function");
        try {
            const value = await MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")));
            console.log(`Fulfilled: ${value}`);
        } catch (error) {
            console.log(`Rejected: ${error}`);
        }
    },
];

(async () => {
    for (const example of examples) {
        try {
            await example();
        } catch (e) {
        }
    }
})();
/* Shows the cosole full height in the snippet */
.as-console-wrapper {
    max-height: 100% !important;
}

的字符串
上面的代码在解析或拒绝promise时主动取消了计时器。根据你的用例,这可能不是必要的,并且使代码有点复杂。这对promise部分没有必要;一旦promise被解决或拒绝,就不能改变,再次调用resolvereject函数对promise没有影响(规范对此很清楚)。但是如果你不取消计时器,计时器在触发之前仍然是挂起的。挂起的promise将阻止Node.js退出,例如,所以如果你在最后一件事上做了一个很长的超时,它可能会延迟退出进程。浏览器不会延迟离开页面与挂起的计时器,所以这不适用于浏览器。所以再次,你的里程可能会有所不同,你可以通过不取消计时器来简化一点。
如果你不关心挂起计时器,MyPromise会更简单:

class MyPromise extends Promise {
    constructor(timeout, 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.
        const haveTimeout = typeof timeout === "number";
        const init = haveTimeout ? callback : timeout;
        super((resolve, reject) => {
            init(resolve, reject);
            if (haveTimeout) {
                setTimeout(() => {
                    reject(new Error(`Promise timed out after ${timeout}ms`));
                }, timeout);
            }
        });
    }
    // Pick your own name of course. (You could even override `resolve` itself
    // if you liked; just be sure to do the same arguments detection we do
    // above in the constructor, since you need to support the standard use of
    // `resolve`.)
    static resolveWithTimeout(timeout, x) {
        if (!x || typeof x.then !== "function") {
            // `x` isn't a thenable, no need for the timeout,
            // fulfill immediately
            return this.resolve(x);
        }
        return new this(timeout, x.then.bind(x));
    }
}

z5btuh9x

z5btuh9x2#

要向任何现有promise添加超时,您可以使用:用途:

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

字符串
再后来:

await withTimeout(5000, doSomethingAsync());

kuarbcqp

kuarbcqp3#

虽然可能没有对promise超时的支持,但您可以竞赛promise:

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

字符串
一个通用的Promise.timeout

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


范例:

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.


可能会有一点代价,因为你实际上创建了3个promise而不是2个。
你可能想设置一个promise,而不是让函数为你构造它。这样你就可以分离关注点,最终你可以专注于与一个新构建的promise竞争,这个新构建的promise将在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.

gblwokeq

gblwokeq4#

这是一个有点老的问题,但是我在研究如何超时一个promise时偶然发现了这个问题。
虽然所有的答案都很好,但我发现使用Promises的bluebird实现是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/ });

字符串
正如你所看到的,这比其他提出的解决方案少了很多工作。我想我会把它放在这里,让人们更容易找到它:)
顺便说一句,我没有参与任何蓝鸟项目,只是发现这个特殊的解决方案非常整洁。

ncecgwcz

ncecgwcz5#

Extension Method是一种方便的解决方案:

fetch("/example")
    .withTimeout(5000);

字符串
这是通过向Promise global object的原型添加一个方法来实现的。“
promiseWithTimeout.js

/** Adds a timeout (in milliseconds) that will reject the promise when expired. */
Promise.prototype.withTimeout =
    function (milliseconds) {
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => reject(new Error("Timeout")), milliseconds);
            return this
                .then(value => {
                    clearTimeout(timeout);
                    resolve(value);
                })
                .catch(exception => {
                    clearTimeout(timeout);
                    reject(exception);
                });
        });
    };

export {}; // only needed if imported as a module


为了获得TypeScript支持,请在定义Promise.prototype.withTimeout之前添加以下声明块:

declare global {
    interface Promise<T> {
        /** Adds a timeout (in milliseconds) that will reject the promise when expired. */
        withTimeout(milliseconds: number): Promise<T>;
    }
}

tgabmvqs

tgabmvqs6#

如果你的代码被放在一个类中,你可以使用一个装饰器。你在utils-decorators库(npm install --save utils-decorators)中有这样的装饰器:

import {timeout} from 'utils-decorators';

class SomeService {

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

字符串
或者你可以使用一个wrapper函数:

import {timeoutify} from 'utils-decorators';

const withTimeout = timeoutify(originalFunc, 3000);

ldfqzlk8

ldfqzlk87#

虽然这里的答案是有效的,但你不应该试图重新发明轮子,而只是使用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()
});

字符串

pb3s4cty

pb3s4cty8#

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

用法

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

相关问题