簡體   English   中英

處理承諾鏈中的多個漁獲

[英]Handling multiple catches in promise chain

我對諾言仍然還很陌生,目前正在使用藍鳥,但是在我不確定如何最好地處理它的情況下。

因此,舉例來說,我在Express應用程序中有一個Promise鏈,如下所示:

repository.Query(getAccountByIdQuery)
        .catch(function(error){
            res.status(404).send({ error: "No account found with this Id" });
        })
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .catch(function(error) {
            res.status(406).send({ OldPassword: error });
        })
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error){
            console.log(error);
            res.status(500).send({ error: "Unable to change password" });
        });

所以我的行為是:

  • 通過ID獲取帳戶
  • 如果此時存在拒絕,請轟炸並返回錯誤
  • 如果沒有錯誤,則將文檔轉換為模型
  • 使用數據庫文檔驗證密碼
  • 如果密碼不匹配,則炸開並返回其他錯誤
  • 如果沒有錯誤,請更改密碼
  • 然后返回成功
  • 如果有其他問題,請返回500

因此,當前的捕獲量似乎並沒有停止鏈接,這是有道理的,所以我想知道是否存在一種方法可以根據錯誤以某種方式強制鏈停止在某個點,或者是否有更好的方法構造這種結構以獲得某種形式的分支行為,例如, if X do Y else Z

任何幫助都會很棒。

此行為完全類似於同步拋出:

try{
    throw new Error();
} catch(e){
    // handle
} 
// this code will run, since you recovered from the error!

這就是.catch的一半-能夠從錯誤中恢復。 可能需要重新拋出信號以指示狀態仍然是錯誤:

try{
    throw new Error();
} catch(e){
    // handle
    throw e; // or a wrapper over e so we know it wasn't handled
} 
// this code will not run

但是,僅此一種方法對您而言不起作用,因為該錯誤將由以后的處理程序捕獲。 真正的問題是,廣義的“ HANDLE ANYTHING”錯誤處理程序通常是一種不好的做法,在其他編程語言和生態系統中卻極少使用。 因此,藍鳥提供了類型化和謂詞性的捕獲。

附加的優點是您的業務邏輯根本不需要(也不應該)知道請求/響應周期。 決定客戶端獲取哪種HTTP狀態和錯誤不是查詢的責任,而隨着應用的增長,您可能希望將業務邏輯(如何查詢數據庫以及如何處理數據)與發送給客戶端的內容分開(什么http狀態代碼,什么文本和什么響應)。

這是我編寫您的代碼的方式。

首先,我得到.Query拋出NoSuchAccountError ,我將從Bluebird已經提供的Promise.OperationalError它。 如果您不確定如何將錯誤歸類,請告訴我。

我還要為AuthenticationError子類化它,然后做類似的事情:

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}

如您所見-它非常干凈,您可以閱讀文本,就像使用說明書一樣,了解過程中發生的情況。 它也與請求/響應分開。

現在,我將從路由處理程序中這樣調用它:

 changePassword(params)
 .catch(NoSuchAccountError, function(e){
     res.status(404).send({ error: "No account found with this Id" });
 }).catch(AuthenticationError, function(e){
     res.status(406).send({ OldPassword: error });
 }).error(function(e){ // catches any remaining operational errors
     res.status(500).send({ error: "Unable to change password" });
 }).catch(function(e){
     res.status(500).send({ error: "Unknown internal server error" });
 });

這樣,邏輯就全部集中在一個地方,而如何為客戶處理錯誤的決定就都集中在一個地方,而且它們不會相互干擾。

.catch工作方式類似於try-catch語句,這意味着最后只需要一個catch:

repository.Query(getAccountByIdQuery)
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error) {
            if (/*see if error is not found error*/) {
                res.status(404).send({ error: "No account found with this Id" });
            } else if (/*see if error is verification error*/) {
                res.status(406).send({ OldPassword: error });
            } else {
                console.log(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        });

我想知道是否存在一種方法可以根據錯誤以某種方式強制鏈條停止在某個點

不可以。除非您拋出一個冒泡直到結束的異常,否則您無法真正“終止”一條鏈。 有關如何執行此操作的信息,請參見本傑明·格倫鮑姆(Benjamin Gruenbaum)的答案

他的模式的派生將不是區分錯誤類型,而是使用具有statusCodebody字段的錯誤,這些錯誤可以從單個通用.catch處理程序發送。 根據您的應用程序結構,他的解決方案可能會更干凈。

或者是否有更好的方法來構造它以獲得某種形式的分支行為

是的,您可以使用promises進行分支 但是,這意味着離開鏈並“返回”嵌套-就像您在嵌套的if-else或try-catch語句中所做的那樣:

repository.Query(getAccountByIdQuery)
.then(function(account) {
    return convertDocumentToModel(account)
    .then(verifyOldPassword)
    .then(function(verification) {
        return changePassword(verification)
        .then(function() {
            res.status(200).send();
        })
    }, function(verificationError) {
        res.status(406).send({ OldPassword: error });
    })
}, function(accountError){
    res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
    console.log(error);
    res.status(500).send({ error: "Unable to change password" });
});

我一直在這樣做:

最終您將捕獲到的東西。 當鏈中途發生錯誤時,就拋出一個錯誤。

    repository.Query(getAccountByIdQuery)
    .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
    .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')        
    .then(changePassword)
    .then(function(){
        res.status(200).send();
    })
    .catch((error) => {
    if (error.name === 'no_account'){
        res.status(404).send({ error: "No account found with this Id" });

    } else  if (error.name === 'wrong_old_password'){
        res.status(406).send({ OldPassword: error });

    } else {
         res.status(500).send({ error: "Unable to change password" });

    }
});

您的其他函數可能看起來像這樣:

function convertDocumentToModel(resultOfQuery) {
    if (!resultOfQuery){
        throw new Error('no_account');
    } else {
    return new Promise(function(resolve) {
        //do stuff then resolve
        resolve(model);
    }                       
}

派對可能要.catch ,但是可以嵌套.catch ,如下所示:

Mozilla開發人員網絡-使用承諾

編輯:我提交了此內容,因為它通常提供所需的功能。 但是,在這種特殊情況下卻不是。 因為正如其他人已經詳細解釋的那樣, .catch應該可以恢復該錯誤。 例如,您不能在多個 .catch回調中向客戶端發送響應,因為沒有顯式return.catch在這種情況下會使用undefined解析它,從而導致繼續進行.then觸發,即使您的鏈沒有真正解決,可能導致隨后的.catch觸發並向客戶端發送另一個響應,從而導致錯誤並可能以您的方式拋出UnhandledPromiseRejection 我希望這個復雜的句子對您有意義。

代替.then().catch()...您可以執行.then(resolveFunc, rejectFunc) 如果您一路處理事情,那么這個承諾鏈會更好。 這是我將其重寫的方式:

repository.Query(getAccountByIdQuery)
    .then(
        convertDocumentToModel,
        () => {
            res.status(404).send({ error: "No account found with this Id" });
            return Promise.reject(null)
        }
    )
    .then(
        verifyOldPassword,
        () => Promise.reject(null)
    )
    .then(
        changePassword,
        (error) => {
            if (error != null) {
                res.status(406).send({ OldPassword: error });
            }
            return Promise.Promise.reject(null);
        }
    )
    .then(
        _ => res.status(200).send(),
        error => {
            if (error != null) {
                console.error(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        }
    );

注意: if (error != null)與最近的錯誤交互有點麻煩。

我認為本傑明·格魯恩鮑姆(Benjamin Gruenbaum)的上述回答是復雜邏輯序列的最佳解決方案,但是對於更簡單的情況,這是我的選擇。 我只使用errorEncountered標志和return Promise.reject()來跳過任何后續的thencatch語句。 所以它看起來像這樣:

let errorEncountered = false;
someCall({
  /* do stuff */
})
.catch({
  /* handle error from someCall*/
  errorEncountered = true;
  return Promise.reject();
})
.then({
  /* do other stuff */
  /* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
  if (errorEncountered) {
    return;
  }
  /* handle error from preceding then, if it was executed */
  /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});

如果您有兩個以上的對/捕獲對,則可能應該使用本傑明·格倫鮑姆的解決方案。 但這適用於簡單的設置。

注意,最后的catch只有return; 而不是return Promise.reject(); ,因為沒有后續的then我們需要跳過,而這只能算作一個未處理的承諾拒絕,該節點不喜歡。 如上所述,最后的catch將返回一個和平解決的承諾。

暫無
暫無

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

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