Bluebird 的超时有多快/效率如何?
How fast / efficient is Bluebird's timeout?
以下示例在大多数情况下会超时(输出 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
我认为这里发生的事情是承诺链的其余部分所花费的时间与来自 .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 中没有计时器精确到毫秒,因为 JS 是单线程的,所有计时器事件都必须经过事件队列,并且它们只在事件得到服务时调用回调(而不是计时器触发时)。也就是说,计时器事件将始终按顺序提供,因此 setTimeout(..., 1000)
将始终出现在 setTimeout(..., 1001)
之前,即使两个回调的执行之间可能不会恰好有 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;
}
错误是第 ret.isPending()
行的存在。这导致 original.isPending() === false
和 ret.isPending() === true
的时间很短,因为 "resolved" 状态还没有从 original
传播到 children。您的代码遇到了极短的时间和 BOOM,您遇到了竞争条件。
以下示例在大多数情况下会超时(输出 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我认为这里发生的事情是承诺链的其余部分所花费的时间与来自 .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 中没有计时器精确到毫秒,因为 JS 是单线程的,所有计时器事件都必须经过事件队列,并且它们只在事件得到服务时调用回调(而不是计时器触发时)。也就是说,计时器事件将始终按顺序提供,因此 setTimeout(..., 1000)
将始终出现在 setTimeout(..., 1001)
之前,即使两个回调的执行之间可能不会恰好有 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;
}
错误是第 ret.isPending()
行的存在。这导致 original.isPending() === false
和 ret.isPending() === true
的时间很短,因为 "resolved" 状态还没有从 original
传播到 children。您的代码遇到了极短的时间和 BOOM,您遇到了竞争条件。