[英]Is it ok to mix .catch() with async/await for error handling?
[英]JavaScript: differences between async error handling with async/await and then/catch
只是想先发制人地说我熟悉 JavaScript 中的 async/await 和 promises,所以不需要为此链接我到一些 MDN 页面。
我有一个 function 来获取用户详细信息并将其显示在 UI 上。
async function someHttpCall() {
throw 'someHttpCall error'
}
async function fetchUserDetails() {
throw 'fetchUserDetails error'
}
function displayUserDetails(userDetails) {
console.log('userDetails:', userDetails)
}
async function fetchUser() {
try {
const user = await someHttpCall()
try {
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (fetchUserDetailsError) {
console.log('fetching user error', fetchUserDetailsError)
}
} catch (someHttpCallError) {
console.log('networking error:', someHttpCallError)
}
}
它首先通过 someHttpCall 调用someHttpCall
,如果成功则继续fetchUserDetails
并且它也成功然后我们通过returndisplayUserDetails
在 Ui 上显示详细信息。
如果someHttpCall
失败,我们将停止并且不进行fetchUserDetails
调用。 换句话说,我们希望将someHttpCall
的错误处理和它的数据处理与fetchUserDetails
我写的 function 带有嵌套的try catch
块,如果嵌套变深,则无法很好地扩展,我试图使用 plain then
和catch
重写它以获得更好的可读性
这是我的第一次尝试
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
问题在于,即使someHttpCall
失败,第二个也会运行, then
displayUserDetails
。 为了避免这种情况,我不得不让之前的.catch
块抛出
所以这是更新版本
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
然而,现在第二次接球将因投掷而被调用。 所以当someHttpCall
失败时,在我们处理了someHttpCallError
错误之后,我们会进入这个块(fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) }
这不好,因为fetchUserDetails
永远不会被调用所以我们应该' t 需要处理fetchUserDetailsError
(我知道someHttpCallError
在这种情况下变成fetchUserDetailsError
)
我可以在其中添加一些条件检查来区分这两个错误,但这似乎不太理想。 所以我想知道如何通过使用.then
和.catch
来实现相同的目标。
我可以在其中添加一些条件检查来区分这两个错误,但这似乎不太理想。
实际上,这听起来像是一个理想的情况。 这意味着您不必嵌套任何可以使您的代码更具可读性的try / catch
块。 这是async / await
旨在解决的问题之一。
一种解决方案可能是通过扩展Error
接口来创建自定义错误,以便能够确定错误发生的内容和位置。
class CustomError extends Error {
constructor(...args) {
super(name, ...args)
this.name = name
}
}
在与错误对应的函数中抛出错误。
async function someHttpCall() {
throw new CustomError('HttpCallError', 'someHttpCall error');
}
async function fetchUserDetails(user) {
throw new CustomError('UserDetailsError', 'fetchUserDetails error')
}
现在,您可以通过检查错误上的name
属性来控制错误流,以区分您的错误。
async function fetchUser() {
try {
const user = await someHttpCall()
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (error) {
switch(error.name) {
case 'HttpCallError':
console.log('Networking error:', error)
break
case 'UserDetailsError':
console.log('Fetching user error', error)
break
}
}
}
我想知道如何通过使用
.then
和.catch
在这里实现相同的目标来改进这一点
如果要复制相同的行为,则无法避免嵌套:
function fetchUser2() {
return someHttpCall().then(
(user) => {
return fetchUserDetails(user).then(
(details) => {
return displayUserDetails(details)
},
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
},
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
}
(与try
/ catch
完全等价的将使用.then(…).catch(…)
而不是 .then( .then(…, …)
,但您可能实际上并不想要那个。)
我写的 function 是 [嵌套的],如果嵌套变深,它就不能很好地扩展,我试图重写它以获得更好的可读性 […]
为此,我建议将await
与.catch()
结合使用:
async function fetchUser() {
try {
const user = await someHttpCall().catch(someHttpCallError => {
throw new Error('networking error', {cause: someHttpCallError});
});
const details = await fetchUserDetails(user).catch(fetchUserDetailsError => {
throw new Error('fetching user error', {cause: fetchUserDetailsError});
});
return displayUserDetails(details);
} catch (someError) {
console.log(someError.message, someError.cause);
}
}
( Error
的cause
选项仍然很新,你可能需要一个polyfill)
我受到Rust的Result
类型的启发,所以我就是这样做的(评论包括在下面):
如果您不熟悉 TypeScript,您可以在上面的 TypeScript Playground 链接(页面右侧)上查看以下代码的纯 JavaScript 版本(没有类型信息)。
// This is the code in my exception-handling utility module:
// exception-utils.ts
export type Result <T = void, E extends Error = Error> = T | E;
export function getError (value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
export function isError <T>(value: T): value is T & Error {
return value instanceof Error;
}
export function assertNotError <T>(value: T): asserts value is Exclude<T, Error> {
if (value instanceof Error) throw value;
}
// This is how to use it:
// main.ts
import {assertNotError, getError, isError, type Result} from './exception-utils.ts';
/**
* Returns either Error or string ID,
* but won't throw because it catches exceptions internally
*/
declare function getStringFromAPI1 (): Promise<Result<string>>;
/**
* Requires ID from API1. Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*/
declare function getNumberFromAPI2 (id: string): Promise<Result<number>>;
/**
* Create version of second function with no parameter required:
* Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*
* The previous two functions work just like this, using the utilities
*/
async function fetchValueFromAPI2 (): Promise<Result<number>> {
try {
const id = await getStringFromAPI1(); // Error or string
assertNotError(id); // throws if `id` is an Error
return getNumberFromAPI2(id); // Error or number
}
catch (ex) {
return getError(ex);
}
}
async function doSomethingWithValueFromAPI2 (): Promise<void> {
const value = await fetchValueFromAPI2(); // value is number or Error
if (isError(value)) {
// handle error
}
else console.log(value); // value is number at this point
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.