2016-12-13 47 views
0

所以,我想模拟一些长时间的计算。为此我计算斐波那契数。如果计算需要很长时间,我需要拒绝它。蓝鸟承诺:为什么不超时?

问题:为什么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 
├── [email protected] 
├── [email protected] 
└── [email protected] 
+0

您使用摩卡吗?您使用的是什么样的跑步者? – Hosar

+0

增加了关于环境的信息 – kharandziuk

+0

你可以用JavaScript处理这种事情的唯一方法就是将你的处理切成小步骤,让其他代码比你的循环运行在这些步骤之间。一种有效的方法将涉及递归式的Promises。最好在WebWorker中,所以你不要拖慢UI过程。 – Touffy

回答

1

如果我在读你的代码正确profib不退出,直到它完成。

超时不是中断。它们只是添加到浏览器/节点运行事件列表中的事件。当前事件的代码完成时,浏览器/节点运行下一个事件。

例子:

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的功能使用。我真的不知道该怎么建议。

+0

您是否尝试运行代码?:) – kharandziuk

+0

是的,我做到了。我遇到了与上面永远循环相同的问题,因为你的代码基本上会运行到完成阶段。除非函数'profib'在计算时退出,否则无法触发超时。所以你会得到你的答案,代码会假设没有超时(超时只有在事件可以被处理时触发)。因此,你不会得到超时错误。 – gman

+0

超时,通常或承诺,真的只适用于异步的东西。你发出网络请求,你的代码退出,其他事件就是进程,如果你没有在超时事件之前得到响应事件,那么你得到超时。但是如果你在一个函数内部旋转,并且永远不会退出,那么超时就没有办法发生。 – gman

0

问题出现是因为承诺以“贪婪”的方式工作(这是我自己的解释)。出于这个原因,函数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)); 
     } 
     }) 
} 
0

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要么最终返回的结果,或抛出一个错误。但是,它会在执行计算时阻止任何其他JavaScript运行。异步执行不是这里的解决方案。或者至少,不是全部。这种CPU密集型任务需要的是WebWorker在另一个JavaScript上下文中运行它。然后,您可以将WebWorker的通信通道包装在Promise中以获取您原先设想的API。

+0

在这种情况下抛出一个错误并不是一个好习惯。我想取消计算并在呼叫者网站上执行超时。我可以这样做:检查我的答案 – kharandziuk