如何在一定时间后超时承诺? 我知道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);
});
}
谢谢!
答案 0 :(得分:41)
原生JavaScript承诺没有任何超时机制。
关于您的实施的问题可能更适合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
构造函数),所以你可能会调整它
另一个问题当然是,大多数时候,你不想构建新的承诺,因此无法使用上述内容。大多数情况下,您已经有了承诺(之前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>
即使回叫首先调用reject
或resolve
,两者都会在计时器到期时调用reject
。这没关系,一旦设定了承诺的已解决状态就无法更改,并且规范定义了对已经解决的承诺的resolve
或reject
的调用,这些承诺不会引发错误。< / p>
但如果它打扰你,你可以包裹resolve
和reject
。这是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种不同的方式来旋转它,但基本的概念是我们传递的resolve
和reject
我们收到的promise执行器是清除计时器的包装器。
但是,它会创建您不需要的函数和额外函数调用。 The spec is clear关于承诺已经解决后解析函数的作用;他们很早就退出了。
答案 1 :(得分:17)
虽然可能没有对承诺超时的支持,但你可以参加承诺:
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;
通用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.
&#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-decorators(npm install --save utils-decorators
)中有这样的装饰器:
import {timeout} from 'utils-decorators';
class SomeService {
@timeout(3000)
doSomeAsync(): Promise<any> {
....
}
}
答案 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>;
};
};