繁体   English   中英

JavaScript:异步错误处理与 async/await 和 then/catch 之间的差异

[英]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 thencatch重写它以获得更好的可读性

这是我的第一次尝试

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);
  }
}

Errorcause选项仍然很新,你可能需要一个polyfill)

我受到RustResult类型的启发,所以我就是这样做的(评论包括在下面):

TS游乐场

如果您不熟悉 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM