This is the fifth day of my participation in the August More text Challenge. For details, see:August is more challenging
preface
The last article was about building interface monitoring for the front-end monitoring system, and since this series focuses on the implementation of the SDK for collecting data on the front-end, this one is about error monitoring, and there’s a lot to learn from the process of collecting errors. We also use the Sourcemap map to handle error stack information for packaged front-end applications.
Common error type of JavaScirpt
-
SyntaxError: SyntaxError
-
Uncaught ReferenceError
-
RangeError
-
TypeError
-
URIError (URL)
-
Resource loading error
-
Interface error
-
Promise failed to catch error
-
Cross-domain script error
Let’s distinguish between those mistakes.
Syntax errors:
<script>
const value = 10
@!
console.log(value)
</script>
Copy the code
When parsing the Scrpit code, the JS engine will first do a lexical analysis and convert the JS into a stream of tokens in [{}] format. Why do this? After the tokens flow is changed to an abstract syntax tree, the engine will have a parser to determine the syntax during the generation of the syntax tree. The code parser will not generate a valid syntax tree and will throw a syntax error. Stop parsing the code.
Throw error: Uncaught SyntaxError: Invalid or unexpected token
Quote error:
<script>
const value = 10
console.log(test)
console.log(value)
</script>
Copy the code
Js engine conducts lexical analysis of the code in the current script. After syntax parsing is completed, an AST syntax tree is formed successfully. At this time, JS engine will precompile the current AST tree. That is, open up space in memory, store variables/functions to the allocated space, declare variables/functions, this variable/function promotion occurs in this stage.
The execution process of the precompile phase
- Create a GO object
- Declared variables are given a GO object with the value undefined, and declared functions are given a GO object with the value of the function body
- When a function is encountered, an AO object is created
- Look for function parameters and variable declarations within the function. The parameter names and variable names are attributes of the AO object, and their values are undefined
- The argument parameters are unified, and the value of the argument is assigned to the parameter
- Find a function declaration with the function name as a property of the AO object and the value as a function reference
- All declared global variables are mounted to window.
When the precompilation is complete, the JS engine will enter the running phase. When the code runs to console.log(test), the scope lookup will be performed. When the top-level window is found, there is no declaration of this variable, then the JS engine will throw an error. Code no longer executes down.
Error test.html:12 Uncaught ReferenceError: Test is not defined
Scope error:
<script>
const value = []
value.length = -1
console.log(value)
</script>
Copy the code
When the precompilation is completed, the JS engine will enter the running stage. When the code runs to value.length = -1, the JS engine finds that the length of value is assigned to -1. The JS engine throws Uncaught RangeError: Invalid Array Length. Because the -1 value is not in the range or collection that the array allows. When an error is thrown, the JS code terminates.
There are several other cases
// The Number object's method arguments are out of range
const num = new Number(12.34);
console.log(num.toFixed(-1));
// The function stack exceeds the maximum value
const foo = () = > foo();
foo(); // RangeError: Maximum call stack size exceeded
Copy the code
Error: Uncaught RangeError: Invalid Array Length
Type error:
<script>
const value = []
const test = {};
test.go();
console.log(value)
</script>
Copy the code
An error that occurs when the type of the value or parameter is not the expected type
Uncaught TypeError: test.go is not a function
Error: URL
<script>
const value = []
decodeURI("%"); // URIError: URI malformed
console.log(value)
</script>
Copy the code
Error caused by using the global URI handler.
Uncaught URIError: URI malformed at decodeURI (
)
Resource loading error:
<script src="./notfound.js"></script>
Copy the code
Resource loading error, that is, if the resource in the website fails to load the resource load error
Throw an error GET http://127.0.0.1:5500/notfound.js net: : ERR_ABORTED 404 (Not Found)
Interface error
axios.get('/notfound')
Copy the code
This error was picked up by the interface monitor in our previous chapter
Throw an error: GET http://127.0.0.1:5501/notfound 404 (Not Found)
Promise failed to catch error
<script>
const value = []
new Promise((resolve, reject) = > {
resolve(a.b)
})
console.log(value)
</script>
Copy the code
Errors that occur in a Promise are uniformly placed in the promise’s catch for handling. If there is no catch, the value is thrown up. Because of the execution mechanism, it does not block the thread from executing, so the value can be printed normally
Thrown error: Uncaught (in promise) ReferenceError: A is not defined
Cross-domain script error
<script src="https://test.bootcdn.net/ajax/libs/test.js"></script>
Copy the code
The front end needs to configure Crossorigin in the script tag to catch errors reported within cross-site scripts, because browsers only allow scripts in the same domain to catch specific errors.
Because maybe some browsers don’t support Crossorigin, we’ll use a try catch to keep throwing it up.
How to capture
-
window.onerror
The callback window.onError is raised when a JavaScript runtime error occurs, including syntax errors and exceptions raised in the handler.
However, window. onError does not catch resource loading errors
-
Use window.addeventListener (‘error’) to catch resource errors, but window.addeventListener (‘error’) can also catch JS runtime errors. .src || target? Href distinguishes between a resource load error and a JS runtime error.
Since window.addeventListener (‘error’) can also catch errors, why do we use window.onError?
Because window. onError has more event object data, it is clearer.
-
window.addEventListener(‘unhandledrejection’)
Catch promise uncaught errors
Why not try/catch
Interviewer: Please describe in one sentence what JS exceptions can be caught by a try catch
async await promise try… catch
Design error monitoring data structures
Js run load time data structure
{
content: // Stack information
col: / / column
row: / / line
message // The main error message
name // The main name of the error
resourceUrl // url
errorMessage // Complete error message
scriptURI / / script urlLineNumber:/ / line number
columnNumber / / column number
}
// If there is an error in the framework code packaged with WebPack, handle it onceAdd {source// The corresponding resource
sourcesContentMap // sourceMap information
}
Copy the code
Resource error data structure
{
url
}
Copy the code
Promise catch error
{
type: / / type
reason / / reasons
}
Copy the code
Design code
- The error is formatted
let formatError = errObj= > {
let col = errObj.column || errObj.columnNumber // Safari is only available in Firefox
let row = errObj.line || errObj.lineNumber // Safari is only available in Firefox
let message = errObj.message
let name = errObj.name
let { stack } = errObj
if (stack) {
let matchUrl = stack.match(/https? :\/\/[^\n]+/) // Match from HTTP? This includes not only the file information but also the line and column information
let urlFirstStack = matchUrl ? matchUrl[0] : ' '
// Obtain the file that reported the error
let regUrlCheck = /https? :\/\/(\S)*\.js/
let resourceUrl = ' '
if (regUrlCheck.test(urlFirstStack)) {
resourceUrl = urlFirstStack.match(regUrlCheck)[0]}let stackCol = null // Get the column information in the statck
let stackRow = null // Retrieve the row information in the statck
let posStack = urlFirstStack.match(/:(\d+):(\d+)/) // // : row: column
if (posStack && posStack.length >= 3) {
;[, stackCol, stackRow] = posStack
}
// TODO formatStack
return {
content: stack,
col: Number(col || stackCol),
row: Number(row || stackRow),
message,
name,
resourceUrl
}
}
return {
row,
col,
message,
name
}
}
Copy the code
- Webpack-packed projects need to be processed separately
let frameError = async errObj => {
const result = await $.get(`http://localhost:3000/sourcemap? col=${errObj.col}&row=${errObj.row}`)
return result
}
Copy the code
-
window.onerror
let _originOnerror = window.onerror window.onerror = async(... arg) => {let [errorMessage, scriptURI, lineNumber, columnNumber, errorObj] = arg let errorInfo = formatError(errorObj) // If the webpack-packed framework code reported an error, the resourceUrl field is temporarily used to distinguish whether the framework code reported an error if (errorInfo.resourceUrl === 'http://localhost:3000/react-app/dist/main.bundle.js') { let frameResult = await frameError(errorInfo) errorInfo.col = frameResult.column errorInfo.row = frameResult.line errorInfo.name = frameResult.name errorInfo.source = frameResult.source errorInfo.sourcesContentMap = frameResult.sourcesContentMap } errorInfo._errorMessage = errorMessage errorInfo._scriptURI = scriptURI errorInfo._lineNumber = lineNumber errorInfo._columnNumber = columnNumber errorInfo.type = 'onerror' cb(errorInfo) _originOnerror && _originOnerror.apply(window, arg) } Copy the code
-
window.onunhandledrejection
let _originOnunhandledrejection = window.onunhandledrejection
window.onunhandledrejection = (. arg) = > {
let e = arg[0]
let reason = e.reason
cb({
type: e.type || 'unhandledrejection',
reason
})
_originOnunhandledrejection && _originOnunhandledrejection.apply(window, arg)
}
Copy the code
- window.addEventListener
window.addEventListener(
'error'.event= > {
// Filter js error
let target = event.target || event.srcElement
let isElementTarget =
target instanceof HTMLScriptElement ||
target instanceof HTMLLinkElement ||
target instanceof HTMLImageElement
if(! isElementTarget)return false
// Report the resource address
let url = target.src || target.href
cb({
url
})
},
true
)
Copy the code
Refer to the article
Interviewer: Please describe in one sentence what JS exceptions can be caught by a try catch
async await promise try… catch
window.onerror &&window.addEventListener(‘error’)
WindowEventHandlers.onunhandledrejection