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

  1. SyntaxError: SyntaxError

  2. Uncaught ReferenceError

  3. RangeError

  4. TypeError

  5. URIError (URL)

  6. Resource loading error

  7. Interface error

  8. Promise failed to catch error

  9. 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

  1. Create a GO object
  2. 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
    1. When a function is encountered, an AO object is created
    2. 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
    3. The argument parameters are unified, and the value of the argument is assigned to the parameter
    4. Find a function declaration with the function name as a property of the AO object and the value as a function reference
  3. 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