如何在Vuejs的异步事件监听器中正确处理拒绝错误?

huangapple go评论72阅读模式
英文:

How to properly handle rejection errors in async event listeners in Vuejs?

问题

Proper way to call an async function from an event listener in Vue.js, while ensuring that any promise rejection errors will bubble up via onErrorCaptured like normal?

如何在Vue.js中从事件侦听器中正确调用异步函数,同时确保任何Promise拒绝错误会像正常一样通过onErrorCaptured冒泡

My first instinct would be to just add async:

我的第一反应是只需添加async

However Vue onErrorCaptured won't be called if the promise is rejected and this violates the eslint rule @typescript-eslint/no-misused-promise

然而,如果Promise被拒绝Vue的onErrorCaptured不会被调用,这违反了eslint规则@typescript-eslint/no-misused-promise”。

Another option would be to remove both await and async:

另一个选择是删除await和async

However this still leads to unhandled promise rejections and violates the rule @typescript-eslint/no-floating-promises.

然而,这仍然会导致未处理的Promise拒绝,并违反规则“@typescript-eslint/no-floating-promises”。

I looked for some way to manually send the rejection error to be handled by Vue onErrorCaptured, Vue exports a function "handleError" but it seems to be for internal use only.

我尝试寻找一种手动发送拒绝错误以供Vue的onErrorCaptured处理的方法Vue导出了一个名为handleError”的函数,但似乎仅供内部使用。

My current workaround is to store the error in a ref and throw from a watchEffect handler:

我的当前解决方法是将错误存储在一个ref中,并从“watchEffect”处理程序中抛出:

How have other people solved this?

其他人是如何解决这个问题的?
英文:

What is the proper way to call an async function from an event listener in vue.js, while ensuring that any promise rejection errors will bubble up via onErrorCaptured like normal?

My first instinct would be to just add async:

window.addEventListener('click', async () => {
  await toggle() // error: no-misused-promise
})

However Vue onErrorCaptured won't be called if the promise is rejected and this violates the eslint rule @typescript-eslint/no-misused-promise

Another option would be to remove both await and async:

window.addEventListener('click', () => {
  toggle() // error: no-floating-promises
})

However this still leads to unhandled promise rejections and violates the rule @typescript-eslint/no-floating-promises.

I looked for some way to manually send the rejection error to be handled by Vue onErrorCaptured, Vue exports a function "handleError" but it seems to be for internal use only.

window.addEventListener('click', () => {
  toggle().catch(handleError) // argument error
})

My current workaround is to store the error in a ref and throw from a watchEffect handler:

const error = ref<Error>()

window.addEventListener('click', () => {
  toggle().catch((err => { error.value = err })
})

watchEffect(() => {
  if (error.value) {
    throw new Error(error.value)
  }
})

How have other people solved this?

答案1

得分: 2

以下是翻译好的部分:

"如果不是来自 Vue API(生命周期钩子、监听器等)和模板事件监听器的错误,Vue 不会处理,需要用户自行处理。

如果需要忽略异步错误,必须明确处理,否则会导致未处理的 Promise 错误:

window.addEventListener('click', async () => {
  try {
    await toggle()
  } catch {}
})

或者:

window.addEventListener('click', () => {
  toggle().catch(() => {});
})

错误处理大致可以按照这里的方式进行。有一些可以改进的地方,new Error(error.value) 会丢弃原始调用堆栈,并导致 error.value 的错误消息格式不正确,而实际上它应该是 Error 的一个实例。一般来说,在这种情况下最好重新抛出错误。

为了处理同步错误,同步监视器更适合。

可以提取成如下可组合的形式:

const useHandleComponentError = () => {
  const error = ref();

  watchSyncEffect(() => {
    if (error.value === undefined) {
      return;
    } else if (error.value instanceof Error) {
      throw error.value;
    } else {
      throw new Error(String(error.value))
    }
  });

  return fn => function errorHandler(...args) {
    let result;
    try {
      result = fn.apply(this, args);
    } catch (err) {
      // 同步错误
      error.value = err;
    }

    if (result?.then) {
      // 类似 Promise 的异步错误
      result.then(null, err => {
        error.value = err;        
      });
    }
  };
};

并像这样使用:

const handleComponentError = useHandleComponentError();

window.addEventListener('click', handleComponentError(async () => {
  throw new Error('whatever')
}))
英文:

The errors that don't come from Vue API (lifecycle hooks, watchers, etc) and template event listeners aren't handled by Vue, this needs to be done by a user.

If asynchronous error needs to be ignored, this needs to be explicitly done because it would result in unhandled promise error otherwise:

window.addEventListener('click', async () => {
  try {
    await toggle()
  } catch {}
})

Or:

window.addEventListener('click', () => {
  toggle().catch(() => {});
})

Error handling can be done roughly the way it was done here. Some things that can be improved, new Error(error.value) would discard the original call stack and result in wrongly formatted error message for error.value being an instance of Error, which is expected. Generally it's preferable to rethrow an error in this case.

In order to handle synchronous errors, synchronous watcher would be suitable more.

It could be extracted to such composable:

const useHandleComponentError = () => {
  const error = ref();

  watchSyncEffect(() => {
    if (error.value === undefined) {
      return;
    } else if (error.value instanceof Error) {
      throw error.value;
    } else {
      throw new Error(String(error.value))
    }
  });
  
  return fn => function errorHandler(...args) {
    let result;
    try {
      result = fn.apply(this, args);
    } catch (err) {
      // Sync error
      error.value = err;
    }
    
    if (result?.then) {
      // Promise-like async error
      result.then(null, err => {
        error.value = err;        
      });
    }
  };
};

And used like:

const handleComponentError = useHandleComponentError();

window.addEventListener('click', handleComponentError(async () => {
  throw new Error('whatever')
}))

huangapple
  • 本文由 发表于 2023年4月11日 16:32:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/75983884.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定