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() === falseret.isPending() === true 的时间很短,因为 "resolved" 状态还没有从 original 传播到 children。您的代码遇到了极短的时间和 BOOM,您遇到了竞争条件。