2016-09-26 77 views
0

我想使用承諾將調用鏈接到不同的庫。在失敗的情況下,庫方法返回一個描述錯誤的對象,但依賴於庫的不同字段。如何標準化承諾鏈中的錯誤對象?

爲了一致地向被調用者報告任何錯誤,我想規範化所有的錯誤對象遵循一個通用的格式。但我不知道如何以優雅的方式使用Bluebird和/或標準諾言API來做到這一點。

在這裏僞JS是我:

var methodAFromLibX_Async = Promise.promisify(...); 
var methodBFromLibY_Async = Promise.promisify(...); 

methodAFromLibX_Async(...) 
.then(function(result) { 
    methodBFromLibY_Async(...) 
    .then(function(result) { ... }) 
    .catch(normalizeAndSendErrorFromLibY); 
}) 
.catch(normalizeAndSendErrorFromLibX); 

上面的代碼似乎工作,但:

  1. 我有normalizeAndSendErrorFromLibYnormalizeAndSendErrorFromLibX
  2. 我之間的冗餘代碼我真正的用例我必須連鎖超過2個電話,並且代碼的金字塔形狀肯定開始看起來像一個回撥地獄...

編輯:爲了更加清楚一點,在這裏我設想的解決方案,但無法實現: chain with parallel path for errors & ok results

+1

是否有任何理由,你不能在你的鏈條的最後,您歸所有可能的錯誤,然後有一個'catch'「送」起來? – m90

+0

謝謝你的評論@ m90。只有最後一個全局性的catch/catchAll纔會泄露錯誤起源的知識。如我的例子中所解釋的,當錯誤來自不同的lib調用時,使用一堆'if..else'塊,我可以推斷出原點。但是,當我在鏈中調用同一個庫的幾個方法時,追溯回溯更復雜。我不敢相信沒有更優雅的做法...... –

+0

你確定這是你想要的鏈嗎? 'normErrorFromY'處理來自'normErrorFromX'的錯誤? – Bergi

回答

1

你正在尋找解決的辦法是

return methodAFromLibX_Async(…) 
.then(function(result) { 
    return methodBFromLibY_Async(…) 
    .then(function(result) { 
      return methodCFromLibX_Async(…) 
      .catch(normalizeAndThrowErrorFromLibX); 
    }, normalizeAndThrowErrorFromLibY); 
}, normalizeAndThrowErrorFromLibX) 
.then(reportSuccess, reportError); 

但是,這是相當醜陋。鑑於您的錯誤處理程序重新拋出錯誤,您也可以使用

return methodAFromLibX_Async(…) 
.catch(normalizeAndThrowErrorFromLibX) 
.then(function(result) { 
    return methodBFromLibY_Async(…) 
    .catch(normalizeAndThrowErrorFromLibY) 
    .then(function(result) { 
      return methodCFromLibX_Async(…) 
      .catch(normalizeAndThrowErrorFromLibX); 
    }); 
}) 
.then(reportSuccess, reportError); 

這仍然不是最佳的。您不希望在每次調用這些函數時都放置.catch(normalise),並且您不希望被迫將它們嵌套。所以更好的因素在他們自己的功能:

function withRejectionHandler(fn, normalise) { 
    return function() { 
     return fn.apply(this, arguments).catch(normalise); 
    }; 
} 
var methodA = withRejectionHandler(methodAFromLibX_Async, normalizeAndThrowErrorFromLibX); 
var methodB = withRejectionHandler(methodBFromLibY_Async, normalizeAndThrowErrorFromLibY); 
var methodA = withRejectionHandler(methodCFromLibX_Async, normalizeAndThrowErrorFromLibX); 

return methodA(…).then(methodB).then(methodC).then(reportSuccess, reportError); 

你可以將它與庫方法的promisification結合起來。

+0

你認爲如何使用['Promise.coroutine'](http://bluebirdjs.com/docs/api/promise.coroutine.html)和一個生成器來生成'methodA,B& C'而不是用'.then()'鏈接承諾。我已經發布了[下面的替代解決方案](http://stackoverflow.com/a/39701754/2363712),但我不太確定'.catch'處理程序將處理錯誤,因爲我認爲它會這樣做。 –

+1

是的,如果'reportSuccess'不僅需要最後一個結果,而且還需要所有結果,那麼這是完全不錯的,[很好的解決方案](http://stackoverflow.com/a/28250697/1048572)。 'catch'將按預期工作,或者您也可以在生成器函數的代碼周圍放置一個'try' /'catch'。 – Bergi

+0

謝謝您確認。我不需要這裏的中間結果,但我會在我的工具箱中保留這個選項! –

1

如果你想避免你正在展示的格局你的例子似乎有另外兩個選項適用於你:

promisify你的庫如圖所示,在你的鏈中正確地傳播錯誤,然後構建一個能夠正常化的函數全部個已知錯誤:

var methodAFromLibX_Async = Promise.promisify(...); 
var methodBFromLibY_Async = Promise.promisify(...); 

methodAFromLibX_Async(...) 
.then(function(result) { 
    return methodBFromLibY_Async(...) 
    .then(function(result) { ... }) 
}) 
.catch(function(err){ 
    if (hasLibXShape(err)){ 
    return Promise.reject(normalizeLibXErr(err)); 
    } else if (hasLibYShape(err)){ 
    return Promise.reject(normalizeLibYErr(err)); 
    } 
}) 
.catch(function(normalizedErr){ 
    // handle normalized error 
}); 

另一種選擇是手動promisify庫中的方法和這裏正常化錯誤:

function promisifiedMethodA(/*...args*/){ 
    var args = [].slice.call(arguments); 
    return new Promise(function(resolve, reject){ 
     methodA.apply(null, args.concat([function(err, data){ 
      if (err) return reject(normalizeMethodAError(err)); 
      resolve(data); 
     }])); 
    }); 
} 

讀你的最新評論我想後者可能適合您的需求更好。

+1

是的,我可以手動promisify我的圖書館方法,並規範化那裏的錯誤。藍鳥文件堅持說這是一種反模式,我甚至沒有考慮過這種選擇。但現在這聽起來是最好的解決方案... –

1

你可以使用藍鳥過濾陷阱:http://bluebirdjs.com/docs/api/catch.html#filtered-catch

而更改您的代碼是這樣的:

var methodAFromLibX_Async = Promise.promisify(...); 
var methodBFromLibY_Async = Promise.promisify(...); 

methodAFromLibX_Async(...) 
.then(function(result) { 
    return methodBFromLibY_Async(...); 
}).then(function(result) { 
    ... 
}).catch({code: 'X_Async', message: 'bad lib X'}, function(e) { 
    //If it is a methodAFromLibX_AsyncError, will end up here because 

}).catch({code: 'Y_Async', message: 'bad lib Y'}, function(e) { 
    //Will end up here if a was never declared at all 

}).catch(function(e) { 
    //Generic catch-the rest, error wasn't methodAFromLibX_AsyncError nor 
    //methodBFromLibY_AsyncError 
}); 
+0

謝謝,但我的庫作爲普通的對象返回錯誤。沒有錯誤的實例,甚至更少來自特定的*子類*的錯誤。你的解決方案是否也適用於這種情況?有沒有一種方法可以將catchAll子句中的公共代碼分解出來? –

+0

你可以嘗試通過代碼或任何對象鍵來過濾它們。 (......) 'fs.readFileAsync(...) .then(...) .catch({code:'ENOENT'},function(e){ console.log(「file not found:」+ e.path ); });' – Anouar

0

作爲一個替代的解決方案,使用藍鳥的Promise.coroutine

/* Bergi's solution to normalize */ 
function withRejectionHandler(fn, normalise) { 
    return function() { 
     return fn.apply(this, arguments).catch(normalise); 
    }; 
} 
var methodA = withRejectionHandler(methodAFromLibX_Async, normalizeAndThrowErrorFromLibX); 
var methodB = withRejectionHandler(methodBFromLibY_Async, normalizeAndThrowErrorFromLibY); 
var methodA = withRejectionHandler(methodCFromLibX_Async, normalizeAndThrowErrorFromLibX); 

/* use of coroutine to sequence the work */ 
var workflow = Promise.coroutine(function*() { 
    var resA = yield methodA(...); 
    var resB = yield methodB(...); 
    var resC = yield methodC(...); 

    reportSuccess(resA, resB, resC); 
}); 

workflow().catch(reportError);