Итак, я пытаюсь смоделировать некоторые длинные вычисления. для этого я вычисляю число Фибоначчи. В случае, когда вычисление занимает много времени, я должен отклонить его.

Вопрос: , почему обработчик 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
0
kharandziuk 13 Дек 2016 в 17:56

3 ответа

Лучший ответ

Если я правильно читаю ваш код, 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. Я действительно не знаю, что предложить.

1
gman 13 Дек 2016 в 16:35

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-код при выполнении вычисления. Асинхронное выполнение здесь не является решением. Или, по крайней мере, не все это. Для таких задач, интенсивно использующих процессор, вам нужен WebWorker для запуска его в другом контексте JavaScript. Затем вы можете обернуть канал связи вашего WebWorker в Promise, чтобы получить API, который вы изначально представляли.

0
Touffy 15 Дек 2016 в 11:07

Проблема возникает из-за того, что обещания работают «жадно» (это мое собственное объяснение). По этой причине функция 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
kharandziuk 15 Дек 2016 в 10:05