蓝鸟承诺:为什么不超时?
Bluebird promise: why it doesn't timeout?
所以,我正在尝试对一些长时间的计算进行建模。为此,我正在计算斐波那契数。如果计算时间太长,我需要拒绝它。
问题: 为什么 TimeoutErrror
处理程序不起作用?如何修复代码?
const expect = require('chai').expect
const Promise = require('bluebird')
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return profib(n, cur, strAdd(cur, prev));
}
})
}
const TIMEOUT = 10000
const N = 20000
describe('recursion', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
let prom = profib(N).timeout(1000)
.catch(Promise.TimeoutError, function(e) {
console.log('timeout', e)
return '-1'
})
return prom.then((num) => {
expect(num).equal('-1')
})
})
})
const strAdd = function(lnum, rnum) {
lnum = lnum.split('').reverse();
rnum = rnum.split('').reverse();
var len = Math.max(lnum.length, rnum.length),
acc = 0;
res = [];
for(var i = 0; i < len; i++) {
var subres = Number(lnum[i] || 0) + Number(rnum[i] || 0) + acc;
acc = ~~(subres / 10); // integer division
res.push(subres % 10);
}
if (acc !== 0) {
res.push(acc);
}
return res.reverse().join('');
};
关于环境的一些信息:
➜ node -v
v6.3.1
➜ npm list --depth=0
├── bluebird@3.4.6
├── chai@3.5.0
└── mocha@3.2.0
如果我没看错你的代码 profib
直到完成后才会退出。
超时不是中断。它们只是添加到 browser/node 到 运行 事件列表中的事件。当当前事件的代码完成时,browser/node 运行 是下一个事件。
示例:
setTimeout(function() {
console.log("timeout");
}, 1);
for(var i = 0; i < 100000; ++i) {
console.log(i);
}
即使超时设置为 1 毫秒,它也不会出现,直到循环结束(在我的机器上大约需要 5 秒)
你可以用一个简单的永远循环看到同样的问题
const TIMEOUT = 10000
describe('forever', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
while(true) { } // loop forever
})
})
运行 在您的环境中,您会发现它永远不会超时。 JavaScript不支持中断,只支持事件。
至于修复代码,您需要插入对 setTimeout 的调用。例如,让我们更改永久循环以使其退出(并因此允许其他事件)
const TIMEOUT = 100
function alongtime(n) {
return new Promise(function(resolve, reject) {
function loopTillDone() {
if (n) {
--n;
setTimeout(loopTillDone);
} else {
resolve();
}
}
loopTillDone();
});
}
describe('forever', function() {
it.only('cancelation', function(done) {
this.timeout(2 * TIMEOUT)
alongtime(100000000).then(done);
})
})
不幸的是,使用 setTimeout 确实是一个缓慢的操作,并且可以说不应该在像 profib
这样的函数中使用。我真的不知道该建议什么。
出现问题是因为 promise 以 "greedy" 方式工作(这是我自己的解释)。由于这个原因,函数 profib 不会释放事件循环。要解决此问题,我需要释放事件循环。最简单的方法是 Promise.delay():
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return Promise.delay(0).then(() => profib(n, cur, strAdd(cur, prev));
}
})
}
gman 已经解释了为什么你的想法行不通。简单有效的解决方案是在循环中添加一个条件来检查时间和中断,如下所示:
var deadline = Date.now() + TIMEOUT
function profib(n, prev = '0', cur = '1') {
if (Date.now() >= deadline) throw new Error("timed out")
// your regular fib recursion here
}
调用 profib
要么最终 return 结果,要么抛出错误。但是,它会在计算时阻止来自 运行ning 的任何其他 JavaScript。异步执行不是这里的解决方案。或者至少,不是全部。对于此类 CPU 密集型任务,您需要一个 WebWorker 来 运行 它在另一个 JavaScript 上下文中。然后,您可以将 WebWorker 的通信通道包装在一个 Promise 中,以获得您最初设想的 API。
所以,我正在尝试对一些长时间的计算进行建模。为此,我正在计算斐波那契数。如果计算时间太长,我需要拒绝它。
问题: 为什么 TimeoutErrror
处理程序不起作用?如何修复代码?
const expect = require('chai').expect
const Promise = require('bluebird')
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return profib(n, cur, strAdd(cur, prev));
}
})
}
const TIMEOUT = 10000
const N = 20000
describe('recursion', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
let prom = profib(N).timeout(1000)
.catch(Promise.TimeoutError, function(e) {
console.log('timeout', e)
return '-1'
})
return prom.then((num) => {
expect(num).equal('-1')
})
})
})
const strAdd = function(lnum, rnum) {
lnum = lnum.split('').reverse();
rnum = rnum.split('').reverse();
var len = Math.max(lnum.length, rnum.length),
acc = 0;
res = [];
for(var i = 0; i < len; i++) {
var subres = Number(lnum[i] || 0) + Number(rnum[i] || 0) + acc;
acc = ~~(subres / 10); // integer division
res.push(subres % 10);
}
if (acc !== 0) {
res.push(acc);
}
return res.reverse().join('');
};
关于环境的一些信息:
➜ node -v
v6.3.1
➜ npm list --depth=0
├── bluebird@3.4.6
├── chai@3.5.0
└── mocha@3.2.0
如果我没看错你的代码 profib
直到完成后才会退出。
超时不是中断。它们只是添加到 browser/node 到 运行 事件列表中的事件。当当前事件的代码完成时,browser/node 运行 是下一个事件。
示例:
setTimeout(function() {
console.log("timeout");
}, 1);
for(var i = 0; i < 100000; ++i) {
console.log(i);
}
即使超时设置为 1 毫秒,它也不会出现,直到循环结束(在我的机器上大约需要 5 秒)
你可以用一个简单的永远循环看到同样的问题
const TIMEOUT = 10000
describe('forever', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
while(true) { } // loop forever
})
})
运行 在您的环境中,您会发现它永远不会超时。 JavaScript不支持中断,只支持事件。
至于修复代码,您需要插入对 setTimeout 的调用。例如,让我们更改永久循环以使其退出(并因此允许其他事件)
const TIMEOUT = 100
function alongtime(n) {
return new Promise(function(resolve, reject) {
function loopTillDone() {
if (n) {
--n;
setTimeout(loopTillDone);
} else {
resolve();
}
}
loopTillDone();
});
}
describe('forever', function() {
it.only('cancelation', function(done) {
this.timeout(2 * TIMEOUT)
alongtime(100000000).then(done);
})
})
不幸的是,使用 setTimeout 确实是一个缓慢的操作,并且可以说不应该在像 profib
这样的函数中使用。我真的不知道该建议什么。
出现问题是因为 promise 以 "greedy" 方式工作(这是我自己的解释)。由于这个原因,函数 profib 不会释放事件循环。要解决此问题,我需要释放事件循环。最简单的方法是 Promise.delay():
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return Promise.delay(0).then(() => profib(n, cur, strAdd(cur, prev));
}
})
}
gman 已经解释了为什么你的想法行不通。简单有效的解决方案是在循环中添加一个条件来检查时间和中断,如下所示:
var deadline = Date.now() + TIMEOUT
function profib(n, prev = '0', cur = '1') {
if (Date.now() >= deadline) throw new Error("timed out")
// your regular fib recursion here
}
调用 profib
要么最终 return 结果,要么抛出错误。但是,它会在计算时阻止来自 运行ning 的任何其他 JavaScript。异步执行不是这里的解决方案。或者至少,不是全部。对于此类 CPU 密集型任务,您需要一个 WebWorker 来 运行 它在另一个 JavaScript 上下文中。然后,您可以将 WebWorker 的通信通道包装在一个 Promise 中,以获得您最初设想的 API。