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?
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')
}))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With