[英]JavaScript: differences between async error handling with async/await and then/catch
Just wanted to preemptively say that I am familiar with async/await and promises in JavaScript so no need to link me to some MDN pages for that.只是想先发制人地说我熟悉 JavaScript 中的 async/await 和 promises,所以不需要为此链接我到一些 MDN 页面。
I have a function to fetch user details and display it on the UI.我有一个 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)
}
}
It first makes HTTP call via someHttpCall
and if it succeeds then it proceeds to fetchUserDetails
and it that succeeds as well then we display the details on Ui via returndisplayUserDetails
.它首先通过 someHttpCall 调用
someHttpCall
,如果成功则继续fetchUserDetails
并且它也成功然后我们通过returndisplayUserDetails
在 Ui 上显示详细信息。
If someHttpCall
failed, we will stop and not make fetchUserDetails
call.如果
someHttpCall
失败,我们将停止并且不进行fetchUserDetails
调用。 In other words, we want to separate the error handling for someHttpCall
and it's data handling from fetchUserDetails
换句话说,我们希望将
someHttpCall
的错误处理和它的数据处理与fetchUserDetails
The function I wrote is with nested try catch
blocks which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability using plain then
and catch
我写的 function 带有嵌套的
try catch
块,如果嵌套变深,则无法很好地扩展,我试图使用 plain then
和catch
重写它以获得更好的可读性
This was my first atttempt这是我的第一次尝试
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
The problem with this is that the second then
will run ie displayUserDetails
even with someHttpCall
failing.问题在于,即使
someHttpCall
失败,第二个也会运行, then
displayUserDetails
。 To avoid this I had to make the previous .catch
blocks throw为了避免这种情况,我不得不让之前的
.catch
块抛出
so this is the updated version所以这是更新版本
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)
}
)
}
However now the second catch will get called as a result of the throw.然而,现在第二次接球将因投掷而被调用。 So when the
someHttpCall
failed, after we handled the someHttpCallError
error, we would enter this block (fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) }
which is not good since fetchUserDetails
never gets called so we shouldn't need to handle fetchUserDetailsError
(I know someHttpCallError
became fetchUserDetailsError
in this case)所以当
someHttpCall
失败时,在我们处理了someHttpCallError
错误之后,我们会进入这个块(fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) }
这不好,因为fetchUserDetails
永远不会被调用所以我们应该' t 需要处理fetchUserDetailsError
(我知道someHttpCallError
在这种情况下变成fetchUserDetailsError
)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.我可以在其中添加一些条件检查来区分这两个错误,但这似乎不太理想。 So I am wondering how I can improve this by using
.then
and .catch
to achieve the same goal here.所以我想知道如何通过使用
.then
和.catch
来实现相同的目标。
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
我可以在其中添加一些条件检查来区分这两个错误,但这似乎不太理想。
Actually, that sounds like an ideal situation.实际上,这听起来像是一个理想的情况。 That means that you don't have to nest any
try / catch
blocks which could make you code a lot more readable.这意味着您不必嵌套任何可以使您的代码更具可读性的
try / catch
块。 This is one of the things that async / await
is meant to solve.这是
async / await
旨在解决的问题之一。
A solution could be is to create custom errors by extending the Error
inteface to be able to determine what and where the error occurs.一种解决方案可能是通过扩展
Error
接口来创建自定义错误,以便能够确定错误发生的内容和位置。
class CustomError extends Error {
constructor(...args) {
super(name, ...args)
this.name = name
}
}
Throw your errors within the functions that correspond with the error.在与错误对应的函数中抛出错误。
async function someHttpCall() {
throw new CustomError('HttpCallError', 'someHttpCall error');
}
async function fetchUserDetails(user) {
throw new CustomError('UserDetailsError', 'fetchUserDetails error')
}
Now you can control your error flow by checking the name
property on the error to differentiate your errors.现在,您可以通过检查错误上的
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
}
}
}
I am wondering how I can improve this by using
.then
and.catch
to achieve the same goal here我想知道如何通过使用
.then
和.catch
在这里实现相同的目标来改进这一点
You don't get to avoid the nesting if you want to replicate the same behaviour:如果要复制相同的行为,则无法避免嵌套:
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
}
)
}
(The exact equivalent to try
/ catch
would use .then(…).catch(…)
instead of .then(…, …)
, but you might not actually want that .) (与
try
/ catch
完全等价的将使用.then(…).catch(…)
而不是 .then( .then(…, …)
,但您可能实际上并不想要那个。)
The function I wrote is [nested] which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability […]
我写的 function 是 [嵌套的],如果嵌套变深,它就不能很好地扩展,我试图重写它以获得更好的可读性 […]
For that, I would recommend to combine await
with .catch()
:为此,我建议将
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);
}
}
(The cause
option for Error
is still quite new, you might need a polyfill for that) (
Error
的cause
选项仍然很新,你可能需要一个polyfill)
I've been inspired by Rust 's Result
type, so this is how I do it (comments included below):我受到Rust的
Result
类型的启发,所以我就是这样做的(评论包括在下面):
If you aren't familiar with TypeScript, you can see the JavaScript-only version of the following code (with no type information) at the TypeScript Playground link above (on the right side of the page).
如果您不熟悉 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.