2012-07-02 87 views
34

我正在使用the Q module來嘗試避免在有很多步驟的場景中出現「厄運金字塔」的Node.js。例如:如何正確地中止一個node.js承諾鏈使用Q?

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).end(); 
} 

基本上這似乎工作;如果任何任務步驟拋出錯誤,則會傳遞給回調函數(儘管我很樂意進行改進,因爲我是node.js promise的新手)。但是,當我需要儘早中止任務鏈時,我遇到了問題。例如,如果成功返回RESULT1我可能要早調用回調並中止休息,但我嘗試這樣做是失敗...

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1) 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      callback(null, result1); 
      return null; 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).end(); 
} 

在這個例子中,我看到兩個「中止!」和「做第3步......」打印。

我確信我只是在誤解這裏的一些基本原則,所以將不勝感激任何幫助。謝謝!

+0

一個解決方案,我發現是創建一個單獨的承諾鏈條後第一鏈條可能會斷裂。在以上示例中,與result2相關的.then語句會附加到step2的Q.ncall,而不是附加到原始的promise。然而,這裏主要的缺點是它在我看來擺脫了Q的主要好處之一:避免厄運的金字塔!它總比沒有承諾好,但我不喜歡解決方案... –

回答

16

所承諾鏈中拋出的任何錯誤都將導致整個電池組,對早期中止,而控制提供給錯誤迴路徑。 (在這種情況下,fail()處理程序)當您檢測到某個導致您希望中止承諾鏈的狀態時,只需拋出一個非常具體的錯誤,並將其記錄在錯誤返回中並忽略(如果您如此選擇)

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1 == 'some failure state I want to cause abortion') 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      throw new Error('abort promise chain'); 
      return null; 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(function(err) { 
     if (err.message === 'abort promise chain') { 
      // just swallow error because chain was intentionally aborted 
     } 
     else { 
      // else let the error bubble up because it's coming from somewhere else 
      throw err; 
     } 
    }) 
    .end(); 
} 
+17

您正在使用控制流的異常,並且通常不建議這樣做。 Kris Kowal給出的解決方案避免了這個問題。 –

+3

'throw'後不需要'return null' – Pepijn

34

這是你需要分支的情況,這意味着嵌套或創建一個子程序。

function doTask(task, callback) { 
    return Q.ncall(task.step1, task) 
    .then(function(result1) { 
     if (result1) return result1; 
     return Q.ncall(task.step2, task) 
     .then(function(result2) { 
      return Q.ncall(task.step3, task); 
     }) 
    }) 
    .nodeify(callback) 
} 

或者

function doTask(task, callback) { 
    return Q.ncall(task.step1, task) 
    .then(function(result1) { 
     if (result1) { 
      return result1; 
     } else { 
      return continueTasks(task); 
     } 
    }) 
    .nodeify(callback) 
} 

function continueTasks(task) { 
    return Q.ncall(task.step2, task) 
    .then(function(result2) { 
     return Q.ncall(task.step3, task); 
    }) 
} 
+0

這是分支的最佳方法嗎?看起來,當有多個分支時,會再次引入縮進。這裏有一個[示例](https://gist.github.com/svenjacobs/3f42bbaf4cbabe2b58b5),我使用[q-io](https://github.com/kriskowal/q-io)執行多個文件操作。我首先檢查是否存在目錄,列出查找某個文件的文件,如果只找到一個匹配的文件,則將其刪除。在那裏有多個應該放棄鏈條的if條件。我使用一個特殊的返回值來檢查這種情況,但必須在每個函數中檢查它。這是一個好方法嗎? –

+4

@SvenJacobs你在這個例子中描述的是異常情況。考慮https://gist.github.com/kriskowal/e98774443eb0f1653871 –

+2

我仍然有這種方法的問題,因爲它使錯誤處理更難。投入承諾鏈(Calvin Alvin的答案)允許有一個'.fail()'來處理流程中的任何錯誤。以這種方式寫作承諾(分支)讓我回到了回撥地獄。 – Pedro

2

我相信你只需要拒絕承諾擺脫承諾鏈。

https://github.com/kriskowal/q/wiki/API-Reference#qrejectreason

也好像.END()已更改爲.done()

function doTask(task, callback) 
{ 
    Q.ncall(task.step1, task) 
    .then(function(result1){ 
     if(result1) 
     {// the rest of the task chain is unnecessary 
      console.log('aborting!'); 
      // by calling Q.reject, your second .then is skipped, 
      // only the .fail is executed. 
      // result1 will be passed to your callback in the .fail call 
      return Q.reject(result1); 
     } 
     return Q.ncall(task.step2, task); 
    }) 
    .then(function(result2){ 
     console.log('doing step 3...'); 
     return Q.ncall(task.step3, task); 
    }) 
    .fail(callback).done(); 
}