蓝鸟承诺:为什么它没有超时?

时间:2016-12-13 14:56:21

标签: javascript node.js promise bluebird

所以,我正在尝试建模一些长计算。为此我正在计算斐波纳契数。如果计算需要花费很多时间,我需要拒绝它。

问题:为什么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

3 个答案:

答案 0 :(得分:1)

如果我正确阅读您的代码,profib在完成之前不会退出。

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

示例:

&#13;
&#13;
setTimeout(function() {
  console.log("timeout");
}, 1);

for(var i = 0; i < 100000; ++i) {
  console.log(i);
}
&#13;
&#13;
&#13;

即使超时设置为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之类的函数中使用。我真的不知道该建议什么。

答案 1 :(得分: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));
        }
      })
 }

答案 2 :(得分: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密集型任务,您需要的是在另一个JavaScript上下文中运行它的WebWorker。然后,您可以将WebWorker的通信通道包装在Promise中,以获取您最初设想的API。