Advanced JS debugging skills: capture and analyze JavaScript errors

Source: Internet
Author: User

Anyway, as long as the refresh does not recur after a JavaScript error occurs, the user can solve the problem through refresh, And the browser will not crash. If it has not happened. This assumption was established before the Single Page App became popular. After the Single Page App is running for a period of time, the status is extremely complex. You may have entered a number of operations before coming here. Do you just need to refresh the Page? Shouldn't the previous operations be completely redone? Therefore, it is necessary to capture and analyze the exception information, and then we can modify the code to avoid affecting the user experience.

Capture exceptions

Written by ourselvesthrow new Error()Of course, we can capture it because we know it very well.throwWhere did I write it. However, exceptions that occur when you call the browser API are not so easy to capture. Some APIs throw exceptions in the standard, and some APIs throw exceptions only in some browsers due to implementation differences or defects. For the former, we can still usetry-catchCapture. For the latter, we must listen to global exceptions and then capture them.

Try-catch

If some browser APIs are known to throw an exception, we need to put the calltry-catchTo prevent the entire program from being invalid due to an error. For examplewindow.localStorageThis is an API that throws an exception when the write data exceeds the capacity limit. This is also true in Safari's private browser mode.

try {
localStorage.setItem('date', Date.now());
} catch (error) {
reportError(error);
}

Another commontry-catchThe applicable scenario is callback. Because the code of the callback function is uncontrollable, we do not know the code quality and whether to call other APIs that will throw exceptions. Do not put the call backtry-catchIt is required.

listeners.forEach(function(listener) {
try {
listener();
} catch (error) {
reportError(error);
}
});
Window. onerror

Fortry-catchIf an exception occurswindow.onerror.

window.onerror =
function(errorMessage, scriptURI, lineNumber) {
reportError({
message: errorMessage,
script: scriptURI,
line: lineNumber
});
}

Be careful not to be clever.window.addEventListenerOrwindow.attachEventTo listenwindow.onerror. Many browsers only implementwindow.onerror, Or onlywindow.onerrorThe implementation is standard. Considering that the draft standard also defineswindow.onerror, We usewindow.onerrorThat's all.

Attribute loss

Suppose we havereportErrorFunctions are used to collect caught exceptions and send them to server-side storage for query and analysis. What information do we want to collect? Useful information includes: Error Type (name), Error message (message), Script file address (script), Line number (line), Column number (column), Stack tracking (stack). If an exception occurstry-catchThe captured information is displayed inErrorObject (supported by mainstream browsers), soreportErrorThis information can also be collected. Howeverwindow.onerrorWe all know that this event function has only three parameters, so the unexpected information of these three parameters is lost.

Serialize messages

IfErrorIf the object is created by ourselveserror.messageIt is controlled by us. Basically, what do we put inerror.messageInside,window.onerrorThe first parameter (message. (The browser will actually make slight changes, such as adding'Uncaught Error: 'Prefix .) Therefore, we can serialize the attributes we are interested in (for exampleJSON.Stringify) And save iterror.messageAnd thenwindow.onerrorRead the deserialization. Of course, this is limited toErrorObject.

Fifth Parameter

Browser vendors also know that you are usingwindow.onerrorSo startwindow.onerrorAdd new parameters. Considering that only row numbers without column numbers do not seem very symmetric, IE first adds the column numbers and places them in the fourth parameter. However, you are more concerned about whether you can obtain the complete stack. So Firefox should put the stack in the fifth parameter. But Chrome said it would be better to take the wholeErrorThe object is placed in the fifth parameter. You can read all attributes, including custom attributes. As a result, Chrome is fast, and a new one is implemented in Chrome 30.window.onerrorAs a result, the standard draft is written in this way.

window.onerror = function(
errorMessage,
scriptURI,
lineNumber,
columnNumber,
error
) {
if (error) {
reportError(error);
} else {
reportError({
message: errorMessage,
script: scriptURI,
line: lineNumber,
column: columnNumber
});
}
}
Attribute Normalization

What we discussed earlierErrorThe object attributes are named based on Chrome.ErrorThe object attributes are named differently. For example, the script file address in Chrome isscriptBut in Firefoxfilename. Therefore, we need a dedicated functionErrorObjects are normalized, that is, different attribute names are mapped to uniform attribute names. For details, refer to this article. Although browser implementations are updated, it is not difficult to manually maintain such a ing table.

Stack trace (stack. This attribute saves the stack information when an exception occurs in plain text. Because the text format used by each browser is different, you also need to manually maintain a regular expression, function name used to extract each frame from plain text (identifier), File (script), Line number (line) And column number (column).

Security restrictions

If you have encountered a message'Script error.'And you will understand what I'm talking about. This is actually a browser's restriction on different origin script files. The reason for this security restriction is as follows: assume that the HTML returned by an online banking user after logon is different from the HTML displayed by an anonymous user, a third-party website can put the website's URIscript.srcAttribute. HTML cannot be parsed as JS, so the browser throws an exception, and this third-party website can determine whether the user has logged on by parsing the exception location. For this reason, the browser filters all exceptions thrown by different source script files, leaving only'Script error.'Such a unchanged message disappears all other attributes.

For websites of a certain scale, it is normal that script files are stored on the CDN, with different origins. Now, even if you build a small website, common frameworks such as jQuery and Backbone can directly reference versions on the public CDN to accelerate user download. Therefore, this security restriction does cause some trouble. As a result, the exception information we collect from Chrome and Firefox is useless.'Script error.'.

CORS

To bypass this restriction, you only need to ensure that the script file and the page itself are the same source. However, if you place a script file on a server without CDN acceleration, will the download speed be reduced? One solution is to keep the script file on the CDN and useXMLHttpRequestDownload the content back through CORS, and then create<script>Tags are injected into the page. The embedded code on the page is of course the same source.

This is easy to say, but there are many details to implement. In a simple example:

<script src="http://cdn.com/step1.js"></script>
<script>
(function step2() {})();
</script>
<script src="http://cdn.com/step3.js"></script>

We all know that if this step 1, step 2, and step 3 have dependencies, they must be executed strictly in this order; otherwise, errors may occur. The browser can request files in step 1 and Step 3 in parallel, but the execution sequence is guaranteed. If we useXMLHttpRequestTo obtain the file content of step 1 and step 3, we need to ensure that the order is correct. In addition, do not forget step 2. When Step 1 is downloaded in non-blocking form, step 2 can be executed. Therefore, we must manually intervene in step 2 and wait until step 1 is complete before execution.

If we already have a complete set of tools to generate different pages on the website<script>Label, we need to adjust this tool<script>Tag changes:

<script>
scheduleRemoteScript('http://cdn.com/step1.js');
</script>
<script>
scheduleInlineScript(function code() {
(function step2() {})();
});
</script>
<script>
scheduleRemoteScript('http://cdn.com/step3.js');
</script>

We need to implementscheduleRemoteScriptAndscheduleInlineScriptThese two functions Ensure that they reference the external script file in the first<script>Labels are defined before, and the remaining<script>Labels are rewritten to the preceding format. Note that the previously executedstep2Function is put into a largercodeFunction.codeThe function will not be executed. It is just a container, so that the original step2 code can be saved without escaping, but will not be executed immediately.

Next, we need to implement a complete mechanism to ensure thatscheduleRemoteScriptThe content andscheduleInlineScriptThe obtained code can be executed one by one in the correct order. The detailed code is not provided here. If you are interested, you can implement it yourself.

Row number lookup

Getting content through CORS and injecting code into the page can break through security restrictions, but it introduces a new problem, that is, line number conflict. Originally passederror.scriptYou can locate a unique script file and then useerror.lineYou can locate a unique row number. Because the code is embedded on the page, multiple<script>The tag does not passerror.scriptHowever, each<script>The line number inside the tag is counted from 1, so we cannot use the exception information to locate the source code location where the error is located.

To avoid line number conflicts, we can waste some line numbers so that every<script>The row numbers used by the actual code in the tag do not overlap with each other. For example, assume that<script>The actual code in the tag cannot exceed 1000 lines, so I can make the first<script>The code in the tag occupies 1st-1000 rows, so that the second<script>The code in the tag occupies 1,001st-2000 rows (the first 1000 rows are inserted), and the third<script>The code of the tag type occupies 2001st-3000 rows (the first 2000 rows are inserted), and so on. Then we usedata-*This information is recorded in properties for reverse query.

<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>
<script
data-src="http://cdn.com/step3.js"
data-line-start="2001"
>
// '\n' * 2000
// code for step 3
</script>

After such processing, if an error occurserror.lineYes3005It means the actualerror.scriptIt should be'http://cdn.com/step3.js'And the actualerror.lineIt should be5. We can referreportErrorThis row number lookup is completed in the function.

Of course, because we cannot ensure that each script file contains only 1000 rows, and some script files may be significantly smaller than 1000 rows, we do not need to assign a fixed interval of 1000 rows to each<script>Label. We can allocate intervals based on the actual number of script rows, as long as each<script>The range used by the tag does not overlap with each other.

Crossorigin attribute

Browser security restrictions on different source content are not limited<script>Label. SinceXMLHttpRequestCORS can be used to break this restriction. Why cannot resources referenced by tags be used directly? Of course this is acceptable.

For<script>Tag reference does not have same-source script file restrictions.Tags reference different image files. IfIf the label is different from the same source<canvas>Used for plotting.<canvas>The status changes to write-only, so that the website cannot steal unauthorized image data from different origins through JavaScript. LaterLabel IntroductioncrossoriginAttribute solves this problem. If you usecrossorigin="anonymous"Is equivalent to anonymous CORS. If 'crossorigin = "use-credentials" is used, it is equivalent to CORS with authentication.

SinceWhy can tags do this?<script>Label cannot do this? So the browser vendor is<script>The tag is added with the samecrossoriginAttribute is used to solve the preceding security restrictions. Currently, Chrome and Firefox have no problem in supporting this attribute. In Safaricrossorigin="anonymous"Ascrossorigin="use-credentials"The result is that if the server only supports anonymous CORS, Safari will fail the authentication. Because the CDN server is designed to return only static content for performance consideration, it is impossible to dynamically return the HTTP Header required for CORS authentication based on the request. Safari cannot use this feature to solve the above problem.

Summary

JavaScript Exception Handling seems very simple. It is no different from other languages, but it is not that easy to capture exceptions and analyze attributes. Although some third-party services provide Google Analytics services that catch JavaScript exceptions, you must do it yourself to understand the details and principles.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.