Front-end engineers know that JavaScript has basic exception handling capabilities. We can thrownewError (), and the browser will throw an exception when we call an API error. However, it is estimated that the vast majority of front-end engineers have not considered collecting such abnormal information. As long as the refresh fails and does not recur, the user can solve the problem through refresh without crashing the browser. 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.throw
Where 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-catch
Capture. 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-catch
To prevent the entire program from being invalid due to an error. For examplewindow.localStorage
This 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-catch
The 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-catch
It is required.
listeners.forEach(function(listener) {
try {
listener();
} catch (error) {
reportError(error);
}
});
Window. onerror
Fortry-catch
If an exception occurswindow.onerror
.
window.onerror =
function(errorMessage, scriptURI, lineNumber) {
reportError({
message: errorMessage,
script: scriptURI,
line: lineNumber
});
}
Be careful not to be clever.window.addEventListener
Orwindow.attachEvent
To listenwindow.onerror
. Many browsers only implementwindow.onerror
, Or onlywindow.onerror
The implementation is standard. Considering that the draft standard also defineswindow.onerror
, We usewindow.onerror
That's all.
Attribute loss
Suppose we havereportError
Functions 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-catch
The captured information is displayed inError
Object (supported by mainstream browsers), soreportError
This information can also be collected. Howeverwindow.onerror
We all know that this event function has only three parameters, so the unexpected information of these three parameters is lost.
Serialize messages
IfError
If the object is created by ourselveserror.message
It is controlled by us. Basically, what do we put inerror.message
Inside,window.onerror
The 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.message
And thenwindow.onerror
Read the deserialization. Of course, this is limited toError
Object.
Fifth Parameter
Browser vendors also know that you are usingwindow.onerror
So startwindow.onerror
Add 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 wholeError
The 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.onerror
As 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 earlierError
The object attributes are named based on Chrome.Error
The object attributes are named differently. For example, the script file address in Chrome isscript
But in Firefoxfilename
. Therefore, we need a dedicated functionError
Objects 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.src
Attribute. 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 useXMLHttpRequest
Download 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: