JS advanced debugging tips: capture and analyze JavaScript errors
- 2020-03-30 02:23:25
- OfStack
Anyway, as long as the JavaScript error after the refresh does not reproduce, then the user can be refreshed to solve the problem, the browser will not crash, when it did not happen. This assumption was true before Single Page apps became popular. Now, the Single Page App has been running for a while and the state is so complicated that the user may have to do a number of inputs to get here. Shouldn't the previous operation be completely redone? So it is still necessary to capture and analyze the exception information, and then we can modify the code to avoid affecting the user experience.
The way exceptions are caught
We wrote our own throw new Error()
and of course we can catch it, because we know where the throw
is. Exceptions that occur when calling browser apis are not always as easy to catch. Some apis are written in the standard to throw exceptions, and some apis are thrown only by individual browsers because of implementation differences or defects. For the former we can also catch by try-catch
, for the latter we must listen for global exceptions and then catch.
try-catch
If some browser apis are known to throw exceptions, then we need to put the call in try-catch
to avoid errors that cause the entire program to enter an illegal state. For example, window.localstorage
is an API that throws an exception when the volume limit for writing data is exceeded, even in Safari's private browsing mode.
The < code > try {
LocalStorage. SetItem (' date 'date. Now ());
{} the catch (error)
ReportError (error);
}
< / code >
Another common try-catch
scenario is a callback. Because the code for the callback function is out of our control, we don't know the quality of the code or whether it will call other apis that throw exceptions. In order not to cause other code after calling the callback to fail due to a callback error, it is necessary to put the call back in try-catch
.
The < code > listeners. ForEach (function (the listener) {
Try {
Listener ();
{} the catch (error)
ReportError (error);
}
});
< / code >
Window. The onerror
For the place that try-catch
does not cover, if there is an exception, it can only be caught by window.onerror
.
The < code > window. Onerror =
Function (errorMessage scriptURI, lineNumber) {
ReportError ({
Message: errorMessage.
Script: scriptURI.
The line: lineNumber
});
}
< / code >
Be careful not to be clever and use window.addeventlistener
or< code> window.attachevent to listen for window.onerror
. Many browsers only implement window.onerror
, or only the implementation of window.onerror
is standard. Considering that the standard draft also defines window.onerror
, we can use window.onerror
.
If the Error
object is created by ourselves, then the error.message
is controlled by us. Basically what we put in the error.message
, the first parameter of window.onerror
(message
) will be. (browsers actually make minor changes, such as adding the 'Uncaught Error: '
prefix.) Therefore, we can serialize the properties we care about (for example, json. Stringify
) and store them in error.message
, and then read them out in window.onerror
and deserialize them. Of course, this is limited to the Error
object that we created ourselves.
Fifth parameter
Browser vendors also know that people are restricted when using window.onerror
, so they began to add new parameters to window.onerror
. Considering that only the row number without the column number seems to be not very symmetric, IE first added the column number, in the fourth parameter. However, people were more concerned about getting the full stack, so Firefox said it would be better to put the stack in the fifth parameter. But Chrome says it's better to put the entire < code > Error < / code > object in the fifth parameter, and you can read any property you want, including custom properties. As a result, the new window.onerror
signature was implemented in Chrome 30 due to the fast action of Chrome, resulting in the standard draft being written in this way.
The < code > window. Onerror = function (
ErrorMessage,
ScriptURI,
LineNumber,
ColumnNumber,
The error
) {
{if (error)
ReportError (error);
} else {
ReportError ({
Message: errorMessage.
Script: scriptURI.
The line: lineNumber,
Column: columnNumber
});
}
}
< / code >
Attribute normalization
The Error
object attribute we discussed before is named based on the way of Chrome. However, different browsers have different ways of naming the Error
object attribute. For example, the address of the script file is called script
but is called filename
in Firefox. Therefore, we also need a special function to normalize the Error
object, that is, map the different attribute names to the unified attribute names. Specific practice can refer to this article. Although the browser implementation will be updated, it is not too difficult to maintain such a mapping table manually.
Similar is the format of the stack trace (stack
). This property stores the stack information of an exception when it occurs in plain text. Since the text format used by each browser is different, it is also necessary to manually maintain a regular expression to extract the function name (identifier
), file (script
), line number (line
) and column number (column
) of each frame from the plain text.
If you've ever had an error with a message of 'Script error.'
, you'll see what I'm talking about. The reason for this security restriction is this: if an e-bank returns different HTML after the user logs in than the anonymous user sees, a third-party website can place the e-bank's URI in the script.src
attribute. HTML, of course, cannot be parsed as JS, so the browser will throw an exception, and the third-party site will be able to determine whether the user is logged in by parsing the exception's location. For this reason, the browser will filter the exception thrown by different source Script files, leaving only 'Script error.
For a site of a certain size, it is normal for script files to be placed on the CDN with different sources. Now, even if you're building your own web site, common frameworks like jQuery and Backbone can refer directly to the version on the public CDN to speed up downloads. So this security restriction did cause some trouble, causing the exception information we collected from Chrome and Firefox to be useless 'Script error.'
.
CORS
To get around this limitation, just make sure that the script file is the same as the page itself. But putting script files on a server without CDN acceleration will slow down the user's download speed. One solution is to keep the script file on the CDN, download the content back through CORS using XMLHttpRequest
, and then create < Script> The
tag is injected into the page. The code embedded in the page is, of course, homologous.
This is simple to say, but there are many details to implement. Here's a simple example:
The < code > < Script SRC = "http://cdn.com/step1.js" > < / script>
< Script>
(function step2 () {}) ();
< / script>
< Script SRC = "http://cdn.com/step3.js" > < / script>
< / code >
We all know that this step1, step2, and step3, if there are dependencies, must be executed in strict accordance with this order, otherwise it may go wrong. The browser can request the files for step1 and step3 in parallel, but the order is guaranteed at execution time. If we ourselves get the contents of step1 and step3 files by XMLHttpRequest
, we need to ensure that the order is correct. In addition, don't forget step2. Step2 can be executed when step1 is downloaded in non-blocking form, so we have to manually intervene with step2 to make it wait for step1 to complete.
If we already had a whole set of tools to generate ; Script>
, we're going to have to adjust the tool to < Script>
tag changed:
The < code > < Script>
ScheduleRemoteScript (' http://cdn.com/step1.js');
< / script>
< Script>
ScheduleInlineScript (the function code () {
(function step2 () {}) ();
});
< / script>
< Script>
ScheduleRemoteScript (' http://cdn.com/step3.js');
< / script>
< / code >
We need to implement the scheduleRemoteScript
and scheduleInlineScript
, and make sure they are the first to reference the < of the external script file. Script> The
tag is defined before, and then the rest of the < Script> The
tag will be rewritten to look like this. Notice that the step2
function that was immediately executed is put into a larger code
function. The code
function will not be executed, it is just a container, so that the original step2 code can be retained without escaping, but will not be executed immediately.
Next we need to implement a complete mechanism to ensure that the contents of the files downloaded from the scheduleRemoteScript
based on their address and the code retrieved directly from the scheduleInlineScript
are executed one after another in the correct order. I will not give the detailed code here, you are interested in their own implementation.
Getting the content through CORS and injecting the code into the page can break the security barrier, but introduces a new problem: line number conflicts. Originally, the unique script file can be located by error.script
, and then the unique line number can be located by error.line
. Now because they're all embedded code, multiple ; Script>
tags cannot be distinguished by error.script
, whereas each < Script> The line Numbers inside the
tag all start at 1, so we can't use the exception information to locate the source code where the error is.
To avoid line number conflicts, we can waste some line Numbers so that each < Script>
tag has the actual code used by the line number interval does not overlap each other. For example, suppose each < Script> The actual code in the
tag is not more than 1000 lines, so I can make the first < Script> The code in the
tag occupies line 1, 1000, leaving the second < Script> The code in the
tag occupies line 1001, 2000 (insert 1000 blank lines before), and the third < Script>
tag type code occupies row 2001, 3000 (previously inserted 2000 blank lines), and so on. Then we use the data-*
attribute to record these information, easy to check.
The < code > < The script
Data - SRC = "http://cdn.com/step1.js"
Data - line - start = "1"
>
// code for step 1
< / script>
< Script data - line - start = "1001" >
/ / '\ n' * 1000
// code for step 2
< / script>
< The script
Data - SRC = "http://cdn.com/step3.js"
Data - line - start = "2001"
>
/ / '\ n' * 2000
// code for step 3
< / script>
< / code >
After this treatment, if an error error.line
is 3005
, that means the actual error.script
should be 'http://cdn.com/step3.js'
, and the actual error.line
should be 0 5
1. We can do this in the previously mentioned reportError
function.
Of course, since there is no way to guarantee that each script file has only 1000 lines, and some script files are obviously less than 1000 lines, it is not necessary to assign a fixed interval of 1000 lines to each script file. Script> < / code > tags. We can assign the interval according to the actual number of footers, as long as we make sure that each one is ; Script>
tags used by the interval is not overlapping.
Crossorigin properties
The browser's security restrictions on content from different sources are certainly not limited to ; Script> < / code > tags. Since
XMLHttpRequest
can break through this limitation with CORS, why not refer to the resource directly through the tag? Of course it can.
According to the < code > < Script> The tag also applies to < Img>
tags refer to different source image files. If a < Img>
tag is a different source, once in < Canvas>
is used when drawing, the < Canvas>
will be changed to a write-only state, ensuring that websites cannot steal unauthorized data from different source images through JavaScript. Then the < code > < Img> The tag solves this problem by introducing the crossorigin
attribute. If crossorigin="anonymous"
, it is equivalent to anonymous CORS; If 'crossorigin= "use-credentials", it is equivalent to CORS with certification.
JavaScript exception handling may seem simple, just like any other language, but it's not that easy to actually catch all the exceptions and analyze the attributes. Although there are some third-party services that provide a Google Analytics like service for catching JavaScript exceptions, you still have to do it yourself to understand the details and principles.