Search code examples
javascriptnode.jspromisebluebird

Bluebird promise: why it doesn't timeout?


So, I'm trying to model some long computation. for this purpose I'm computing the fibonacci number. In case when computation takes to much time I need to reject it.

The question: why TimeoutErrror handler doesn't work? How to fix the code?

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('');
};

Some info about environment:

➜  node -v
v6.3.1
➜  npm list --depth=0
├── bluebird@3.4.6
├── chai@3.5.0
└── mocha@3.2.0

Solution

  • If I'm reading your code correctly profib does not exit until it's finished.

    Timeouts are not interrupts. They are just events added to the list of events for the browser/node to run. The browser/node runs the next event when the code for the current event finishes.

    Example:

    setTimeout(function() {
      console.log("timeout");
    }, 1);
    
    for(var i = 0; i < 100000; ++i) {
      console.log(i);
    }

    Even though the timeout is set for 1 millisecond it doesn't appear until after the loop finishes (Which takes about 5 seconds on my machine)

    You can see the same problem with a simple forever loop

    const TIMEOUT = 10000
    
    describe('forever', function() {
      it.only('cancelation', function() {
        this.timeout(2 * TIMEOUT)
    
        while(true) { }   // loop forever
      })
    })
    

    Run in with your environment and you'll see it never times out. JavaScript does not support interrupts, it only supports events.

    As for fixing the code you need to insert a call to setTimeout. For example, let's change forever loop so it exits (and therefore allows other events)

    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);
      })
    })
    

    Unfortunately using setTimeout is really a slow operation and arguably shouldn't be used in a function like profib. I don't really know what to suggest.