如果未能及时完成,NodeJS 会超时一个 Promise
NodeJS Timeout a Promise if failed to complete in time
如何在一定时间后使承诺超时?
我知道 Q 有一个 promise 超时,但我使用的是原生 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);
});
}
谢谢!
本机 JavaScript 承诺没有任何超时机制。
关于您的实现的问题可能更适合 http://codereview.stackexchange.com,但有几点要注意:
您没有提供在承诺中实际执行任何操作的方法,并且
您的 setTimeout
回调中不需要 clearTimeout
,因为 setTimeout
安排了一次性计时器。
既然一个承诺一旦 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
});
(或者可能稍微不那么复杂,请参阅下面的分隔线。)
我有点担心语义不同这一事实(没有 new
,而您确实将 new
与 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));
}
}
用法(如果构建一个新的承诺):
let p = new MyPromise(300, (resolve, reject) => {
// ...
});
p.then((value) => {
// ...
})
.catch((error) => {
// ...
});
用法(如果使用您已有的承诺):
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;
}
上面的代码在解决或拒绝承诺时主动取消计时器。根据您的用例,这可能不是必需的,并且会使代码稍微复杂一些。事情的承诺部分没有必要;一旦承诺被解决或拒绝,就不能更改,再次调用 resolve
或 reject
函数对承诺没有影响(the spec 对此很清楚)。但是,如果您不取消计时器,则计时器在触发之前仍处于等待状态。例如,挂起的 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));
}
}
虽然可能不支持承诺超时,但您可以竞争承诺:
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 个承诺而不是 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.
这是一个有点老的问题,但我在寻找如何使承诺超时时偶然发现了这个问题。
虽然所有答案都很棒,但我发现使用 bluebird implementation of Promises as the easiest way of 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/ });
如您所见,这比其他提议的解决方案要少得多。我想我会把它放在这里让人们更容易找到它:)
顺便说一句,我没有以任何方式参与蓝鸟项目,只是发现这个特定的解决方案非常简洁。
要为任何现有承诺添加超时,您可以使用:
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());
虽然这里的答案是有效的,但您不应该尝试重新发明轮子,而只是使用 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()
});
如果您的代码放在 class 中,您可以为此使用装饰器。你在 utils-decorators library (npm install --save utils-decorators
) 中有这样的装饰器:
import {timeout} from 'utils-decorators';
class SomeService {
@timeout(3000)
doSomeAsync(): Promise<any> {
....
}
}
或者您可以使用包装函数:
import {timeoutify} from 'utils-decorators';
const withTimeout = timeoutify(originalFunc, 3000);
在这种情况下包装器会很方便
用法
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>;
};
};
Extension Method 是一个方便的解决方案:
fetch("/example")
.withTimeout(5000);
这是通过在prototype of the 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>;
}
}
如何在一定时间后使承诺超时? 我知道 Q 有一个 promise 超时,但我使用的是原生 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);
});
}
谢谢!
本机 JavaScript 承诺没有任何超时机制。
关于您的实现的问题可能更适合 http://codereview.stackexchange.com,但有几点要注意:
您没有提供在承诺中实际执行任何操作的方法,并且
您的
setTimeout
回调中不需要clearTimeout
,因为setTimeout
安排了一次性计时器。既然一个承诺一旦 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
});
(或者可能稍微不那么复杂,请参阅下面的分隔线。)
我有点担心语义不同这一事实(没有 new
,而您确实将 new
与 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));
}
}
用法(如果构建一个新的承诺):
let p = new MyPromise(300, (resolve, reject) => {
// ...
});
p.then((value) => {
// ...
})
.catch((error) => {
// ...
});
用法(如果使用您已有的承诺):
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;
}
上面的代码在解决或拒绝承诺时主动取消计时器。根据您的用例,这可能不是必需的,并且会使代码稍微复杂一些。事情的承诺部分没有必要;一旦承诺被解决或拒绝,就不能更改,再次调用 resolve
或 reject
函数对承诺没有影响(the spec 对此很清楚)。但是,如果您不取消计时器,则计时器在触发之前仍处于等待状态。例如,挂起的 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));
}
}
虽然可能不支持承诺超时,但您可以竞争承诺:
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 个承诺而不是 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.
这是一个有点老的问题,但我在寻找如何使承诺超时时偶然发现了这个问题。
虽然所有答案都很棒,但我发现使用 bluebird implementation of Promises as the easiest way of 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/ });
如您所见,这比其他提议的解决方案要少得多。我想我会把它放在这里让人们更容易找到它:)
顺便说一句,我没有以任何方式参与蓝鸟项目,只是发现这个特定的解决方案非常简洁。
要为任何现有承诺添加超时,您可以使用:
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());
虽然这里的答案是有效的,但您不应该尝试重新发明轮子,而只是使用 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()
});
如果您的代码放在 class 中,您可以为此使用装饰器。你在 utils-decorators library (npm install --save utils-decorators
) 中有这样的装饰器:
import {timeout} from 'utils-decorators';
class SomeService {
@timeout(3000)
doSomeAsync(): Promise<any> {
....
}
}
或者您可以使用包装函数:
import {timeoutify} from 'utils-decorators';
const withTimeout = timeoutify(originalFunc, 3000);
在这种情况下包装器会很方便
用法
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>;
};
};
Extension Method 是一个方便的解决方案:
fetch("/example")
.withTimeout(5000);
这是通过在prototype of the 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>;
}
}