簡體   English   中英

藍鳥承諾:為什么不超時?

[英]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直到完成才退出。

超時不是中斷。 它們只是添加到事件列表中以供瀏覽器/節點運行的事件。 當前事件的代碼完成時,瀏覽器/節點將運行下一個事件。

例:

 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這樣的函數中使用profib 我真的不知道建議什么。

出現問題是因為諾言以“貪婪”的方式工作(這是我自己的解釋)。 因此,函數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最終會返回結果,或者拋出錯誤。 但是,在進行計算時,它將阻止其他任何JavaScript運行。 異步執行不是這里的解決方案。 或至少不是全部。 對於此類CPU密集型任務,您需要WebWorker在另一個JavaScript上下文中運行它。 然后,您可以將WebWorker的通信通道包裝在Promise中,以獲取您最初設想的API。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM