2016-08-09 199 views
6

驗證參數並返回函數中的錯誤很常見。JavaScript回調錯誤處理

然而,在JavaScript回調函數,如:

function myFunction(num, callback) { 
    if (typeof num !== 'number') return callback(new Error('invalid num')) 
    // do something else asynchronously and callback(null, result) 
} 

我寫了很多這樣的功能,但我不知道是否有什麼東西可能有害。因爲在大多數情況下,調用者認爲這是一個異步函數,並且回調將在函數調用後的代碼之後執行。但如果某些參數無效,該函數將立即調用回調函數。所以調用者必須小心處理這種情況,即意外的執行順序。

我想聽聽關於這個問題的一些建議。我是否應該認真考慮所有異步回調可能會立即執行?或者我應該使用類似setTimeout(...,0)的東西來將同步事物轉換爲異步事件。或者有更好的解決方案,我不知道。謝謝。

回答

3

的API應當記錄,它會調用回調要麼同步(如Array#sort)或異步(如Promise#then),然後始終服從,所記錄的保證。它不應該混合搭配。

所以是的,如果你有一個通常會異步調用回調函數,它應該總是異步調用它,而不管它爲什麼進行調用。在jQuery中有一個很好的例子:當jQuery第一次添加「延遲」對象時,如果延期已經結算,他們會同步調用回調,但是如果沒有,則是異步調用。這是很多混淆和錯誤的根源,這也是ES2015承諾保證thencatch回調總是被異步調用的原因之一。


如果可能的話,而不是在與代碼庫的休息機會,看看使用Promises而不是簡單的回調。 Promise爲異步操作(以及與同步操作的互操作)提供了非常清晰,簡單,有保證的語義和可組合性。

+0

我刪除了我的downvote。我猜測函數被調用的方式嚴重到足以引發生產,而且不應該在生產中發生。 –

+0

@PatrickRoberts:我已經刪除了答案的這一部分,這與問題並不相關。我可以看到你的觀點,儘管我還沒有同意它。我需要更多地考慮它。我注意到如果你在設置一個ES2015 Promise('let p = new Promise(resolve => {throw new Error();})')的時候拋出它,它會被Promise構造器轉換爲拒絕,贊成單一錯誤渠道的觀點,因爲這種API的設計思想和經驗...... –

+0

我的方法也是不正確的。正如評論者指出的那樣,callstack是非常重要的考慮事項,特別是當開發人員使用你的函數試圖無限期地重試時,如果你的函數在出現錯誤時是同步的,最終會導致一個stackoverflow。 –

-1

那麼,由於調用者期望函數是異步執行回調函數立即或在幾秒鐘內沒有什麼區別。

因爲您有回調函數,所以您不需要執行return

+0

'返回'是爲了提前退出函數,而不是提供一個值。 –

+0

對不起,沒有看到那裏的評論。 –

0

不,立即回撥並不是有害的,事實上有意拖延錯誤只會浪費時間和開銷。是的,立即回撥錯誤可能是非常有害的,對於被假定爲異步的函數應該是避免的! (看,180!)

從開發人員的角度來看,爲什麼只能在以後完成設置有很多很好的理由。例如here

const server = net.createServer(() => {}).listen(8080); 

server.on('listening',() => {}); 

listening事件沒有附着直到.listen(8080)被調用之後,因爲該事件源從調用返回到.listen()。在這種情況下,執行事件以在執行.listen()後同步調用將不成功。

這裏想提出我另一種情況:

var num = '5'; 

myFunction(num, function callback(err, result) { 
    if (err) { 
    return myFunction(num, callback); 
    } 

    // handle result 
}); 

現在,如果你callback與同步的錯誤,這個控制流程將導致計算器。雖然這是開發人員的錯,但從預計會異步的函數中發生的stackoverflow是一件非常糟糕的事情。這是使用setImmediate()傳遞錯誤而不是立即執行callback的一個優點。

0

異步函數的調用者應該知道調用函數的結果是什麼。對於什麼是異步函數應該返回一個標準Promise。

如果您的函數返回Promise,任何人都可以輕鬆理解該函數中發生了什麼。承諾具有拒絕回調,但我們可以爭論是否應通過拒絕承諾來處理參數的驗證,或者是否應該直接引發異常。無論哪種方式,如果調用者使用catch方法正確處理異常,則直接拋出的異常和拒絕將以相同的方式捕獲。

function throwingFunction(num) { 
    return new Promise(function (resolve, reject) { 

    if (typeof num !== 'number') throw new Error('invalid num'); 
    // do something else asynchronously and callback(null, result) 
    }; 
} 

function rejectingFunction(num) { 
    return new Promise(function (resolve, reject) { 

    if (typeof num !== 'number') reject(new Error('invalid num')); 
    // do something else asynchronously and callback(null, result) 
    }; 
} 

// Instead of passing the callback, create the promise and provide your callback to the `then` method. 

var resultThrowing = throwingFunction(num) 
    .then(function (result) { console.log(result); }) 
    .catch(function (error) { console.log(error); }); 

var resultRejecting = rejectingFunction(num) 
    .then(function (result) { console.log(result); }) 
    .catch(function (error) { console.log(error); }); 

這兩種模式都會導致錯誤被捕獲和記錄。

如果使用promise,異步函數的調用者將不必擔心函數內部的實現,並且您可以隨時拋出錯誤或拒絕承諾。