JavaScript:async/await 和 then/catch 异步错误处理的区别
JavaScript: differences between async error handling with async/await and then/catch
只是想先发制人地说我熟悉 async/await 和 JavaScript 中的承诺,所以不需要 link 我去一些 MDN 页面。
我有一个功能可以获取用户详细信息并将其显示在 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
进行 HTTP 调用,如果成功则继续进行 fetchUserDetails
并且它也成功然后我们通过 [=18 在 Ui 上显示详细信息=].
如果someHttpCall
失败,我们将停止而不进行fetchUserDetails
调用。换句话说,我们想将 someHttpCall
的错误处理和 fetchUserDetails
的数据处理分开
我写的函数是嵌套的 try catch
块,如果嵌套变深,它就不能很好地扩展,我试图使用普通的 then
和 [=25 重写它以获得更好的可读性=]
这是我的第一次尝试
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
问题在于第二个 then
将 运行 即 displayUserDetails
即使 someHttpCall
失败。为了避免这种情况,我不得不让之前的 .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
永远不会被调用所以我们不应该需要处理 fetchUserDetailsError
(我知道 someHttpCallError
在这种情况下变成了 fetchUserDetailsError
)
我可以在其中添加一些条件检查来区分这两个错误,但它似乎不太理想。所以我想知道如何通过使用 .then
和 .catch
来实现相同的目标来改进这一点。
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
实际上,这听起来很理想。这意味着您不必嵌套任何 try / catch
块,这会使您的代码更具可读性。这是 async / await
要解决的问题之一。
一种解决方案是通过扩展 Error
界面来创建自定义错误,以便能够确定错误发生的方式和位置。
class CustomError extends Error {
constructor(name, ...args) {
super(...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)
return displayUserDetails(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
如果您想复制相同的行为,则无法避免嵌套:
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(…)
instead of .then(…, …)
, but 。)
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 […]
为此,我建议将 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's Result
类型的启发(它迫使你处理一路上的每一个潜在错误)。
所以我所做的是在每个单独的函数中处理异常,并且绝不允许抛出异常,而是 return 抛出错误(如果出现问题)或所需的 return 值(如果没有异常发生)。这是我如何做的一个例子(包括评论):
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).
// 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
}
只是想先发制人地说我熟悉 async/await 和 JavaScript 中的承诺,所以不需要 link 我去一些 MDN 页面。
我有一个功能可以获取用户详细信息并将其显示在 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
进行 HTTP 调用,如果成功则继续进行 fetchUserDetails
并且它也成功然后我们通过 [=18 在 Ui 上显示详细信息=].
如果someHttpCall
失败,我们将停止而不进行fetchUserDetails
调用。换句话说,我们想将 someHttpCall
的错误处理和 fetchUserDetails
我写的函数是嵌套的 try catch
块,如果嵌套变深,它就不能很好地扩展,我试图使用普通的 then
和 [=25 重写它以获得更好的可读性=]
这是我的第一次尝试
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
问题在于第二个 then
将 运行 即 displayUserDetails
即使 someHttpCall
失败。为了避免这种情况,我不得不让之前的 .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
永远不会被调用所以我们不应该需要处理 fetchUserDetailsError
(我知道 someHttpCallError
在这种情况下变成了 fetchUserDetailsError
)
我可以在其中添加一些条件检查来区分这两个错误,但它似乎不太理想。所以我想知道如何通过使用 .then
和 .catch
来实现相同的目标来改进这一点。
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
实际上,这听起来很理想。这意味着您不必嵌套任何 try / catch
块,这会使您的代码更具可读性。这是 async / await
要解决的问题之一。
一种解决方案是通过扩展 Error
界面来创建自定义错误,以便能够确定错误发生的方式和位置。
class CustomError extends Error {
constructor(name, ...args) {
super(...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)
return displayUserDetails(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
如果您想复制相同的行为,则无法避免嵌套:
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(…)
instead of .then(…, …)
, but
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 […]
为此,我建议将 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's Result
类型的启发(它迫使你处理一路上的每一个潜在错误)。
所以我所做的是在每个单独的函数中处理异常,并且绝不允许抛出异常,而是 return 抛出错误(如果出现问题)或所需的 return 值(如果没有异常发生)。这是我如何做的一个例子(包括评论):
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).
// 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
}