簡體   English   中英

Javascript Promise Pattern - 區分錯誤

[英]Javascript Promise Pattern - differentiate error

考慮這個代碼塊:

    getUser(userId)
    .catch(function(error){
        crashreporter.reportError('User DB failed', error);
        // show user a generic error
    })
    .then(function(user) {
        return chargeCreditCard(user);
    })
    .catch(function(error){
        crashreporter.reportError('Credit card failed', error);
        // show user an error saying that their credit card got rejected
    })

顯然,這樣做的問題是如果用戶數據庫失敗,THEN(USER) 塊就會被執行。 另一種選擇是將第一個捕獲塊移動到鏈的末端。 然而,這會導致另一個問題! 我們將無法區分錯誤是來自用戶數據庫還是信用卡。

我認為可以解決問題的以下模式是否被視為 Promise Anti Pattern? 有沒有更好的方法來做到這一點? 我看到的問題是,您可能會陷入半回調地獄。

    getUser(userId)
    .then(function(user) {
        return chargeCreditCard(user)
              .catch(function(error){
                  crashreporter.reportError('Credit card failed', error);
                  // show user an error saying that their credit card got rejected
              });
    })
    .catch(function(error){
        crashreporter.reportError('User DB failed', error);
        // show user a generic error
    })

編輯:我想我還不是很清楚。 如果有更多 THEN 塊怎么辦,如下所示。 問題是,一旦您遇到一個 ERROR,您就根本不希望鏈條繼續下去。

getUser(userId)
    .then(function(user) {
        return chargeCreditCard(user);
    }, function(error){
        crashreporter.reportError('User DB failed', error);
        // show user a error 1
    })
    .then(function(chargeId) {
        return saveChargeId(chargeId);
    }, function(error){
        crashreporter.reportError('ChargeId DB failed', error);
        // show user a error 2
    })
    .then(function(chargeHistoryId) {
        return associateChargeToUsers(chargeHistoryId);
    }, function(error){
        crashreporter.reportError('chargeHistoryId DB failed', error);
        // show user a error 3
    })
    .catch(function(error){
        crashreporter.reportError('Credit card failed', error);
        // show user a error 4
    })

我認為可以解決問題的以下模式是否被視為 Promise Anti Pattern?

不,還好。

有沒有更好的方法來做到這一點?

是的,有看的區別.then(…).catch(…).then(…, …) 如果你想嚴格區分成功案例(繼續)和錯誤案例(報告這個特定問題),向then傳遞兩個回調是一個更好的主意。 這樣,外部處理程序不能由成功案例代碼的失敗觸發,只能由安裝它的承諾中的失敗觸發。 在你的情況下:

getUser(userId)
.then(function(user) {
    return chargeCreditCard(user)
    .then(function(chargeId) {
        return saveChargeId(chargeId)
        .then(function(chargeHistoryId) {
            return associateChargeToUsers(chargeHistoryId);
            .then(function(result) {
                return finalFormatting(result);
            }, function(error){
                crashreporter.reportError('chargeHistoryId DB failed', error);
                return 3;
            });
        }, function(error){
            crashreporter.reportError('ChargeId DB failed', error);
            return 2;
        });
    }, function(error){
        crashreporter.reportError('Credit card failed', error);
        return 4;
    });
}, function(error){
    crashreporter.reportError('User DB failed', error);
    return 1;
})
.then(showToUser);

盡管您可能想使用通用錯誤處理程序:

getUser(userId)
.catch(function(error){
    crashreporter.reportError('User DB failed', error);
    throw new Error(1);
})
.then(function(user) {
    return chargeCreditCard(user)
    .catch(function(error){
        crashreporter.reportError('Credit card failed', error);
        throw new Error(4);
    });
})
.then(function(chargeId) {
    return saveChargeId(chargeId);
    .catch(function(error){
        crashreporter.reportError('ChargeId DB failed', error);
        throw new Error(2);
    });
})
.then(function(chargeHistoryId) {
    return associateChargeToUsers(chargeHistoryId);
    .catch(function(error){
        crashreporter.reportError('chargeHistoryId DB failed', error);
        throw new Error(3);
    });
})
.then(function(result) {
    return finalFormatting(result);
}, function(error) {
    return error.message;
})
.then(showToUser);

在這里,每個then回調確實返回一個承諾,該承諾本身會因適當的錯誤而拒絕。 理想情況下,每個被調用的函數都已經這樣做了,當它們沒有這樣做並且您需要為每個函數附加一個特定的catch ,您可能想要使用包裝器輔助函數(可能作為crashreporter一部分?)。

function withCrashReporting(fn, msg, n) {
    return function(x) {
        return fn(x)
        .catch(function(error){
            crashreporter.reportError(msg, error);
            throw new Error(n);
        });
    };
}
withCrashReporting(getUser, 'User DB failed', 1)(userId)
.then(withCrashReporting(chargeCreditCard, 'Credit card failed', 4))
.then(withCrashReporting(saveChargeId, 'ChargeId DB failed', 2))
.then(withCrashReporting(associateChargeToUsers, 'chargeHistoryId DB failed', 3))
.then(finalFormatting, function(error) {
    return error.message;
})
.then(showToUser);

我看到的問題是,您可能會陷入半回調地獄。

不,這只是適當的包裝級別。 與回調地獄相反,它可以被壓縮到最多兩個嵌套,並且它總是有一個返回值。

如果您絕對想避免嵌套和回調,請使用async / await ,盡管這實際上更難看:

try {
    var user = await getUser(userId);
} catch(error) {
    crashreporter.reportError('User DB failed', error);
    return showToUser(1);
}
try {
    var chargeId = chargeCreditCard(user);
} catch(error) {
    crashreporter.reportError('Credit card failed', error);
    return showToUser(4);
}
try {
    var chargeHistoryId = saveChargeId(chargeId);
} catch(error) {
    crashreporter.reportError('ChargeId DB failed', error);
    return showToUser(2);
}
try {
    var result = associateChargeToUsers(chargeHistoryId);
} catch(error) {
    crashreporter.reportError('chargeHistoryId DB failed', error);
    return showToUser(3);
}
return showToUser(finalFormatting(result));

這是您無需嵌套回調即可完成“取消”鏈的方法。 這就是為什么承諾是“回調地獄”的解決方案......

var errors = {
    USER: 'User DB failed',
    CHARGE: 'ChargeId DB failed',
    HISTORY: 'chargeHistoryId DB failed',
};

function onError(type, error) {
    throw {
         message: type,
         error: error
    }
}

getUser(userId)
  .then(chargeCreditCard, onError.bind(null, errors.USER))
  .then(saveChargeId, function (error) {
    if (error.message === errors.USER) throw error
    else onError(errors.CHARGE, error);
  })
  .then(associateChargeToUsers, function(error) {
    if (error.message === errors.CHARGE || error.message === errors.USER) throw error
    else onError(errors.HISTORY, error);
  })
  .then(finalFormatting, function(error) {
    crashreporter.reportError(error.message, error.error); 
  })
  .then(showToUser);

本質上,如果第一個 Promise 失敗,您會將其錯誤傳遞到失敗回調鏈中,並在到達末尾時將其記錄到日志中。 沒有創建其他承諾,因此您在第一次失敗時有效地“取消”了操作。

您的鏈應該只有一個catch ,但是您可以為每個函數中拋出的錯誤添加更多上下文。 例如,當chargeCreditCard出現錯誤情況時,您可以將與您要報告的內容相對應的message屬性chargeCreditCard到錯誤上。 然后在您的catch錯誤處理程序中,您可以將該message屬性傳遞給報告者:

getUser(userId)
  .then(chargeCreditCard)
  .catch(reportError);

function reportError(error) {
  crashreporter.reportError(error.message, error);
}

暫無
暫無

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

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