Mature products have higher stability requirements. Only the front-end needs to do a lot of monitoring and error reporting, especially the back-end. An unconsidered anomaly may cause data errors, service avalanches, memory overflow and other problems. Every day, the handling of the grief is abnormal, which can cause online failures.
Assuming there is no error in the code logic, then the rest is abnormal error.
Since any service or code may have external calls, as long as there is uncertainty in the external call, the code may have exceptions, so catching exceptions is a very important basic skill.
So this week I will read the How to avoid uncaught async errors in Javascript see how JS catches asynchronous exception errors.
Overview
The reason for paying attention to asynchronous exceptions is that catching synchronous exceptions is very simple:
try {
;(() => {
throw new Error('err')
})()
} catch (e) {
console.log(e) // caught
}
But asynchronous errors cannot be caught directly, which is not intuitive:
try {
;(async () => {
throw new Error('err') // uncaught
})()
} catch (e) {
console.log(e)
}
The reason is that the asynchronous code is not try catch
. The only synchronous logic is to create an asynchronous function, so errors in the asynchronous function cannot be caught.
To catch async
function, you can call .catch
, because the async
function returns a Promise:
;(async () => {
throw new Error('err')
})().catch((e) => {
console.log(e) // caught
})
Of course, you can also use try catch
directly in the function body:
;(async () => {
try {
throw new Error('err')
} catch (e) {
console.log(e) // caught
}
})()
Similarly, if you catch an exception in the loop body, you must use Promise.all
:
try {
await Promise.all(
[1, 2, 3].map(async () => {
throw new Error('err')
})
)
} catch (e) {
console.log(e) // caught
}
That await
thrown in the modified Promise abnormal, can be try catch
capture.
But it does not mean that the await
will be caught by writing 06135763512017. One case is that the Promise contains an asynchronous:
new Promise(() => {
setTimeout(() => {
throw new Error('err') // uncaught
}, 0)
}).catch((e) => {
console.log(e)
})
This situation can only be caught by throwing an exception reject
new Promise((res, rej) => {
setTimeout(() => {
rej('err') // caught
}, 0)
}).catch((e) => {
console.log(e)
})
Another situation is that this await
has not been executed to:
const wait = (ms) => new Promise((res) => setTimeout(res, ms))
;(async () => {
try {
const p1 = wait(3000).then(() => {
throw new Error('err')
}) // uncaught
await wait(2000).then(() => {
throw new Error('err2')
}) // caught
await p1
} catch (e) {
console.log(e)
}
})()
p1
throws an exception after waiting 3s, but because the err2
exception is thrown after 2s, the code execution is interrupted, so await p1
will not be executed, causing this exception to not be caught.
What's more interesting is that if you change a scene, execute p1
in advance, wait 1s and then await p1
, then the exception will change from being unable to be caught to being able to be caught, so what will the browser do?
const wait = (ms) => new Promise((res) => setTimeout(res, ms))
;(async () => {
try {
const p1 = wait(1000).then(() => {
throw new Error('err')
})
await wait(2000)
await p1
} catch (e) {
console.log(e)
}
})()
The conclusion is that the browser will throw an uncaught exception after 1 second, but after 1 second, the uncaught exception disappears and becomes a caught exception.
This behavior is very strange, and it is difficult to troubleshoot when the program is complex, because parallel Promises are recommended to be handled by Promise.all:
await Promise.all([
wait(1000).then(() => {
throw new Error('err')
}), // p1
wait(2000),
])
In addition, Promise errors will be passed along the Promise chain, so it is recommended to rewrite the multiple asynchronous behaviors in the Promise into a multi-chain mode, and the error will be catch
at 06135763512157 at the end.
As in the previous example, Promise cannot catch internal asynchronous errors:
new Promise((res, rej) => {
setTimeout(() => {
throw Error('err')
}, 1000) // 1
}).catch((error) => {
console.log(error)
})
But if it is written as a Promise Chain, it can be captured:
new Promise((res, rej) => {
setTimeout(res, 1000) // 1
})
.then((res, rej) => {
throw Error('err')
})
.catch((error) => {
console.log(error)
})
The reason is that the Promise Chain is used instead of multiple asynchronous nesting inside, so that multiple asynchronous behaviors will be disassembled into the synchronous behaviors corresponding to the Promise Chain, and the Promise can be captured.
Finally, the errors thrown in the DOM event listener cannot be caught:
document.querySelector('button').addEventListener('click', async () => {
throw new Error('err') // uncaught
})
The same is true for synchronization:
document.querySelector('button').addEventListener('click', () => {
throw new Error('err') // uncaught
})
It can only be captured try catch
intensive reading
We mentioned at the beginning that we need to monitor all exceptions. It try catch
and then
, because these are local error capture methods. When we cannot guarantee that all codes have handled exceptions, global exception monitoring is required. Generally, There are two ways:
window.addEventListener('error')
window.addEventListener('unhandledrejection')
error
can monitor all synchronous and asynchronous runtime errors, but cannot monitor syntax, interface, and resource loading errors. And unhandledrejection
can monitor the errors thrown in Promise that are not .catch
In specific front-end frameworks, some problems can also be solved through the error monitoring solutions provided by the framework, such as React's Error Boundaries and Vue's error handler . One is at the UI component level and the other is global.
Looking back, the try catch
provided by js itself is very effective, and the reason why you often encounter errors that cannot be caught is mostly because of asynchrony.
However, most asynchronous errors can be solved by await
. The only thing we need to pay attention to is that await
only supports one layer, or one chain of error monitoring. For example, this example can detect errors:
try {
await func1()
} catch (err) {
// caught
}
async function func1() {
await func2()
}
async function func2() {
throw Error('error')
}
In other words, as long as the chain is occupied by await
, then the outermost try catch
can catch asynchronous errors. But if there is a layer of asynchrony out of await
, then it cannot be captured:
async function func2() {
setTimeout(() => {
throw Error('error') // uncaught
})
}
To solve this problem, the original text also provides Promise.all
, chained Promise, .catch
etc. Therefore, as long as you pay attention to the asynchronous processing when writing the code, you can use try catch
catch these asynchronous errors.
Summarize
Regarding the handling of asynchronous errors, if there are other circumstances that have not been considered, please leave a message to add.
The discussion address is: Intensive Reading of "Capturing All Asynchronous Errors" · Issue #350 · dt-fe/weekly
If you want to participate in the discussion, please click here , there is a new theme every week, weekend or Monday. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public number
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Freely reproduced-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。