Front-end engineers all know that JavaScript has basic exception handling capabilities. We can throw new Error(), and the browser will also throw an exception when we make an error when calling the API. But it is estimated that most front-end engineers have never considered collecting this abnormal information
Anyway, as long as the JavaScript error does not reappear after refreshing, the user can solve the problem by refreshing, and the browser will not crash. Just pretend that it did not happen. This assumption held true before Single Page Apps became popular. The status of the current Single Page App becomes extremely complicated after running for a period of time. The user may have performed several input operations before arriving here. Should it be refreshed when it is said to be refreshed? Shouldn’t the previous operation be completely redone? So we still need to capture and analyze these exception information, and then we can modify the code to avoid affecting the user experience.
How to catch exceptions
We wrote our own throw new Error(). Of course we can catch it if we want to, because we know exactly where throw is written. However, exceptions that occur when calling browser APIs are not necessarily easy to catch. Some APIs are written in the standards to throw exceptions, and some APIs are only thrown by individual browsers due to implementation differences or defects. For the former, we can also catch it through try-catch. For the latter, we must listen to the global exception and then catch it.
try-catch
If some browser APIs are known to throw exceptions, then we need to put the call in try-catch to prevent the entire program from entering an illegal state due to errors. For example, window.localStorage is such an API. It will throw an exception after writing data exceeds the capacity limit. This will also be the case in Safari's private browsing mode.
For areas that are not covered by try-catch, if an exception occurs, it can only be caught through window.onerror.
Attributes lost
Suppose we have a reportError function that collects captured exceptions and then sends them to server-side storage in batches for query and analysis. What information do we want to collect? The more useful information includes: error type (name), error message (message), script file address (script), line number (line), column number (column), and stack trace (stack). If an exception is caught through try-catch, this information is on the Error object (supported by mainstream browsers), so reportError can also collect this information. But if it is captured through window.onerror, we all know that this event function only has 3 parameters, so the unexpected information of these 3 parameters is lost.
Serialized message
If the Error object is created by ourselves, then error.message is controlled by us. Basically, whatever we put into error.message, the first parameter (message) of window.onerror will be. (The browser will actually make slight modifications, such as adding the 'Uncaught Error: ' prefix.) Therefore, we can serialize the properties we care about (such as JSON.Stringify) and store them in error.message, and then read them in window.onerror Just take it out and deserialize it. Of course, this is limited to Error objects we create ourselves.
The fifth parameter
Browser manufacturers also know the limitations that people face when using window.onerror, so they began to add new parameters to window.onerror. Considering that only the row number but no column number seems not very symmetrical, IE first adds the column number and puts it in the fourth parameter. However, everyone is more concerned about whether they can get the complete stack, so Firefox said it would be better to put the stack in the fifth parameter. But Chrome said that it is better to put the entire Error object in the fifth parameter. You can read any properties you want, including custom properties. As a result, Chrome moved faster and implemented a new window.onerror signature in Chrome 30, which led to the standard draft being written accordingly.
The names of the Error object attributes we discussed before are based on Chrome’s naming method. However, different browsers name the Error object attributes differently. For example, the script file address is called script in Chrome but is called filename in Firefox. . Therefore, we also need a special function to normalize the Error object, that is, to map different attribute names to unified attribute names. For specific methods, please refer to this article. Although browser implementations will be updated, maintaining such a mapping table manually is not too difficult.
Similar to the stack trace format. This attribute saves a stack of exception information in the form of plain text. Since the text format used by each browser is different, it is also necessary to maintain a regular expression manually to extract the function of each frame from the plain text. Name (identifier), file (script), line number (line) and column number (column).
Security restrictions
If you have also encountered an error with the message 'Script error.', you will understand what I am talking about. This is actually a browser limitation for script files from different origins. The reason for this security restriction is this: assuming that the HTML returned by an online bank after a user logs in is different from the HTML seen by anonymous users, a third-party website can put the URI of the online bank into the script.src attribute. Of course, HTML cannot be parsed as JS, so the browser will throw an exception, and the third-party website can determine whether the user is logged in by parsing the location of the exception. For this reason, the browser filters all exceptions thrown by script files from different sources until only a single unchanged message like 'Script error.' is left, and all other attributes disappear.
For websites of a certain scale, it is normal for script files to be placed on a CDN with different sources. Now even if you build your own small website, common frameworks such as jQuery and Backbone can directly reference the versions on public CDNs to speed up user downloads. So this security restriction does cause some trouble, causing the exception information we collect from Chrome and Firefox to be useless 'Script error.'
CORS
To bypass this restriction, just ensure that the script file and the page itself have the same origin. But putting the script file on a server that is not accelerated by CDN will not slow down the user's download speed? One solution is to continue placing the script file on the CDN, use XMLHttpRequest to download the content back through CORS, and then create a <script> tag to inject it into the page. The code embedded in the page is of course from the same source. </p>
<p>This sounds simple, but there are many details to implement. To use a simple example: </p>
<p></p>
<div class="codetitle">
<span><a style="CURSOR: pointer" data="85881" class="copybut" id="copybut85881" onclick="doCopy('code85881')"><u>Copy code</u></a></span> The code is as follows:</div>
<div class="codebody" id="code85881">
<br>
<script src="<a href="http://cdn.com/step1.js"></script">http://cdn.com/step1.js"></script</a>><br>
<script><br>
(function step2() {})();<br>
</script>
http://cdn.com/step3.js">>
If we already have a set of tools to generate <script> tags for different pages on the website, we need to adjust this set of tools to make changes to the <script> tags: </p>
<p></p>
<div class="codetitle">
<span><a style="CURSOR: pointer" data="68128" class="copybut" id="copybut68128" onclick="doCopy('code68128')"><u>Copy code</u></a></span> The code is as follows:</div>
<div class="codebody" id="code68128">
<br>
<script><br>
scheduleRemoteScript('http://cdn.com/step1.js');<br>
</script>
<script><br>
scheduleInlineScript(function code() {<br>
(function step2() {})();<br>
});<br>
</script>
<script><br>
scheduleRemoteScript('http://cdn.com/step3.js');<br>
</script>
Of course, since we cannot guarantee that each script file has only 1000 lines, and some script files may be significantly less than 1000 lines, there is no need to allocate a fixed range of 1000 lines to each