以下示例在大多数情况下超时(输出timed out
):
Promise = require('bluebird');
new Promise(resolve => {
setTimeout(resolve, 1000);
})
.timeout(1001)
.then(() => {
console.log('finished');
})
.catch(error => {
if (error instanceof Promise.TimeoutError) {
console.log('timed out');
} else {
console.log('other error');
}
});
这是否意味着Bluebird的承诺开销需要超过1毫秒?
即使我使用.timeout(1002)
,我也会看到它经常超时。
询问的主要原因 - 我正在试图确定安全阈值是什么,哪个更重要,更短的超时。
在Node.js 8.1.2下使用Bluebird 3.5.0
答案 0 :(得分:1)
我已经在Bluebird的代码中追踪了你的错误。考虑一下:
const p = new Promise(resolve => setTimeout(resolve, 1000));
const q = p.timeout(1001); // Bluebird spawns setTimeout(fn, 1001) deep inside
看起来很无辜,是吗?虽然,不是在这种情况下。在内部,Bluebird实现了类似的东西(实际上并不是有效的JS;省略了超时清除逻辑):
Promise.prototype.timeout = function(ms) {
const original = this;
let result = original.then(); // Looks like noop
setTimeout(() => {
if result.isPending() {
make result rejected with TimeoutError; // Pseudocode
}
}, ms);
return result;
}
Bug存在行ret.isPending()
。它导致original.isPending() === false
和ret.isPending() === true
的短暂时间,因为"已解决"状态尚未从original
传播到儿童。你的代码达到极短的时间和BOOM,你有竞争条件。
答案 1 :(得分:0)
我认为这里发生的事情是,承诺链的其余部分与.timeout()
的计时器之间存在竞争。因为他们在时间上都非常接近,所以有时会获胜,有时会获胜 - 他们很有活力。当我运行记录事件序列的代码时,我会在不同的运行中得到不同的顺序。确切的输出顺序是不可预测的(例如racy)。
const Promise = require('bluebird');
let buffer = [];
function log(x) {
buffer.push(x);
}
new Promise(resolve => {
setTimeout(() => {
log("hit my timeout");
resolve();
}, 1000);
}).timeout(1001).then(() => {
log('finished');
}).catch(error => {
if (error instanceof Promise.TimeoutError) {
log('timed out');
} else {
log('other error');
}
});
setTimeout(() => {
console.log(buffer.join("\n"));
}, 2000);
有时输出:
hit my timeout
finished
而且,有时输出:
hit my timeout
timed out
正如评论中所提到的,如果.then()
总是通过微任务执行(应该在任何宏任务之前),那么人们会认为.then()
会先于setTimeout()
.timeout()
,但事情显然不是那么简单。
由于promise .then()
调度的细节不是规范要求的(只有堆栈没有应用程序代码),代码设计不应该采用特定的调度算法。因此,接近执行它所遵循的异步操作的超时可能是活泼的,因此是不可预测的。
如果你能准确解释你想要解决的问题,我们可能会提供更多具体建议。 Javascript中没有定时器对于ms是精确的,因为JS是单线程的,并且所有定时器事件都必须通过事件队列,并且它们仅在事件被服务时调用它们的回调(不是在定时器触发时)。也就是说,定时器事件将始终按顺序提供,因此setTimeout(..., 1000)
将始终位于setTimeout(..., 1001)
之前,即使执行两个回调之间可能没有正好1ms的增量。