2016-12-13 150 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