└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Errors Handbook 2 | 3 | This README contains information that I've learned over the years about dealing with JavaScript errors, reporting them to the server, and navigating through a lot of bugs that can make this all really hard. Browsers have improved in this area, but there is still room left to improve to make sure that all applications can sanely and soundly handle any error that happens. 4 | 5 | Test cases for content found in this guide can be found at https://mknichel.github.io/javascript-errors/. 6 | 7 | **Table of Contents** 8 | 9 | * [Introduction](#introduction) 10 | * [Anatomy of a JavaScript Error](#anatomy-of-a-javascript-error) 11 | * [Producing a JavaScript Error](#producing-a-javascript-error) 12 | * [Error Messages](#error-messages) 13 | * [Stack Trace Format](#stack-trace-format) 14 | * [Catching JavaScript Errors](#catching-javascript-errors) 15 | * [window.onerror](#windowonerror) 16 | * [try/catch](#trycatch) 17 | * [Protected Entry Points](#protected-entry-points) 18 | * [Promises](#promises) 19 | * [Web Workers](#web-workers) 20 | * [Chrome Extensions](#chrome-extensions) 21 | 22 | ## Introduction 23 | 24 | Catching, reporting, and fixing errors is an important part of any application to ensure the health and stability of the application. Since JavaScript code is also executed on the client and in many different browser environments, staying on top of JS Errors from your application can also be hard. There are no formal web specs for how to report JS errors which cause differences in each browser's implementation. Additionally, there have been many bugs in browsers' implementation of JavaScript errors as well that have made this even harder. This page navigates through these aspects of JS Errors so that future developers can handle errors better and browsers will hopefully converge on standardized solutions. 25 | 26 | ## Anatomy of a JavaScript Error 27 | 28 | A JavaScript error is composed of two primary pieces: the **error message** and the **stack trace**. The error message is a string that describes what went wrong, and the stack trace describes where in the code the error happened. JS Errors can be produced either by the browser itself or thrown by application code. 29 | 30 | ### Producing a JavaScript Error 31 | 32 | A JS Error can be thrown by the browser when a piece of code doesn't execute properly, or it can be thrown directly by code. 33 | 34 | For example: 35 | 36 | ```javascript 37 | var a = 3; 38 | a(); 39 | ``` 40 | 41 | In this example, a variable that is actually a number can't be invoked as a function. The browser will throw an error like `TypeError: a is not a function` with a stack trace that points to that line of code. 42 | 43 | A developer might also want to throw an error in a piece of code if a certain precondition is not met. For example 44 | 45 | ```javascript 46 | if (!checkPrecondition()) { 47 | throw new Error("Doesn't meet precondition!"); 48 | } 49 | ``` 50 | 51 | In this case, the error will be `Error: Doesn't meet precondition!`. This error will also contain a stack trace that points to the appropriate line. Errors thrown by the browser and application code can be handled the same. 52 | 53 | There are multiple ways that developers can throw an error in JavaScript: 54 | 55 | * `throw new Error('Problem description.')` 56 | * `throw Error('Problem description.')` <-- equivalent to the first one 57 | * `throw 'Problem description.'` <-- bad 58 | * `throw null` <-- even worse 59 | 60 | Throwing a string or null is really not recommended since the browser will not attach a stack trace to that error, losing the context of where that error ocurred in the code. It is best to throw an actual Error object, which will contain the error message as well as a stack trace that points to the right lines of code where the error happened. 61 | 62 | ### Error Messages 63 | 64 | Each browser has its own set of messages that it uses for the built in exceptions, such as the example above for trying to call a non-function. Browsers will try to use the same messages, but since there is no spec, this is not guaranteed. For example, both Chrome and Firefox use `{0} is not a function` for the above example while IE11 will report `Function expected` (notably also without reporting what variable was attempted to be called). 65 | 66 | However, browsers tend to diverge often as well. When there are multiple default statements in a `switch` statement, Chrome will throw `"More than one default clause in switch statement"` while Firefox will report `"more than one switch default"`. As new features are added to the web, these error messages have to be updated. These differences can come into play later when you are trying to handle reported errors from obfuscated code. 67 | 68 | You can find the templates that browsers use for error messages at: 69 | 70 | * Firefox - http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg 71 | * Chrome - https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js 72 | * Internet Explorer - https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h 73 | 74 | **![error message warning](https://mknichel.github.io/javascript-errors/ic_warning_black_18px.svg) Browsers will produce different error messages for some exceptions.** 75 | 76 | ### Stack Trace Format 77 | 78 | The stack trace is a description of where the error happened in the code. It is composed of a series of frames, where each frames describe a particular line in the code. The topmost frame is the location where the error was thrown, while the subsequent frames are the function call stack - or how the code was executed to get to that point where the error was thrown. Since JavaScript is usually concatenated and minified, it is also important to have column numbers so that the exact statement can be located when a given line has a multitude of statements. 79 | 80 | A basic stack trace in Chrome looks like: 81 | 82 | ``` 83 | at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) 84 | at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3 85 | ``` 86 | 87 | Each stack frame consists of a function name (if applicable and the code was not executed in the global scope), the script that it came from, and the line and column number of the code. 88 | 89 | Unfortunately, there is no standard for the stack trace format so this differs by browser. 90 | 91 | Microsoft Edge and IE 11's stack trace looks similar to Chrome's except it explicitly lists Global code: 92 | 93 | ``` 94 | at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3) 95 | at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3) 96 | ``` 97 | 98 | Firefox's stack trace looks like: 99 | 100 | ``` 101 | throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9 102 | @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3 103 | ``` 104 | 105 | Safari's format is similar to Firefox's format but is also slightly different: 106 | 107 | ``` 108 | throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18 109 | global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13 110 | ``` 111 | 112 | The same basic information is there, but the format is different. 113 | 114 | Also note that in the Safari example, aside from the format being different than Chrome, the column numbers are different than both Chrome and Firefox. The column numbers also can deviate more in different error situations - for example in the code `(function namedFunction() { throwError(); })();`, Chrome will report the column for the `throwError()` function call while IE11 reports the column number as the start of the string. These differences will come back into play later when the server needs to parse the stack trace for reported errors and deobfuscate obfuscated stack traces. 115 | 116 | See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack for more information on the stack property of errors. When accessing the Error.stack property, Chrome does include the error message as part of the stack but Safari 10+ does not. 117 | 118 | **![stack trace format warning](https://mknichel.github.io/javascript-errors/ic_warning_black_18px.svg) The format of stack traces is different by browser in form and column numbers used.** 119 | 120 | Diving in more, there are a lot of nuances to stack trace formats that are discussed in the below sections. 121 | 122 | #### Naming anonymous functions 123 | 124 | By default, anonymous functions have no name and either appear as empty string or "Anonymous function" in the function names in the stack trace (depending on the browser). To improve debugging, you should add a name to all functions to ensure it appears in the stack frame. The easiest way to do this is to ensure that anonymous functions are specified with a name, even if that name is not used anywhere else. For example: 125 | 126 | ```javascript 127 | setTimeout(function nameOfTheAnonymousFunction() { ... }, 0); 128 | ``` 129 | 130 | This will cause the stack trace to go from: 131 | 132 | ``` 133 | at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17 134 | ``` 135 | 136 | to 137 | 138 | ``` 139 | at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31) 140 | ``` 141 | 142 | In Safari, this would go from: 143 | 144 | ``` 145 | https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27 146 | ``` 147 | 148 | to 149 | 150 | ``` 151 | nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41 152 | ``` 153 | 154 | This method ensures that `nameOfTheAnonymousFunction` appears in the frame for any code from inside that function, making debugging much easier. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips for more information. 155 | 156 | ##### Assigning functions to a variable 157 | 158 | Browsers will also use the name of the variable or property that a function is assigned to if the function itself does not have a name. For example, in 159 | 160 | ```javascript 161 | var fnVariableName = function() { ... }; 162 | ``` 163 | 164 | browsers will use `fnVariableName` as the name of the function in stack traces. 165 | 166 | ``` 167 | at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9) 168 | at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37) 169 | ``` 170 | 171 | 172 | Even more nuanced than that, if this variable is defined within another function, all browsers will use just the name of the variable as the name of the function in the stack trace except for Firefox, which will use a different form that concatenates the name of the outer function with the name of the inner variable. Example: 173 | 174 | ```javascript 175 | function throwErrorFromInnerFunctionAssignedToVariable() { 176 | var fnVariableName = function() { throw new Error("foo"); }; 177 | fnVariableName(); 178 | } 179 | ``` 180 | 181 | will produce in Firefox: 182 | 183 | ``` 184 | throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37 185 | ``` 186 | 187 | In other browsers, this would look like: 188 | 189 | ``` 190 | at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37) 191 | ``` 192 | 193 | **![inner function Firefox stack frame warning](https://mknichel.github.io/javascript-errors/ic_warning_black_18px.svg) Firefox uses different stack frame text for functions defined within another function.** 194 | 195 | ##### displayName Property 196 | 197 | The display name of a function can also be set by the `displayName` property in all major browsers except for IE11. In these browsers, the displayName will appear in the devtools debugger, but in all browsers but Safari, it will **not** be used in Error stack traces (Safari differs from the rest by also using the displayName in the stack trace associated with an error). 198 | 199 | ```javascript 200 | var someFunction = function() {}; 201 | someFunction.displayName = " # A longer description of the function."; 202 | ``` 203 | 204 | There is no official spec for the displayName property, but it is supported by all the major browsers. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName and http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/ for more information on displayName. 205 | 206 | **![IE11 no displayName property](https://mknichel.github.io/javascript-errors/ic_bug_report_black_18px.svg) IE11 doesn't support the displayName property.** 207 | 208 | **![Safari displayName property bug](https://mknichel.github.io/javascript-errors/ic_bug_report_black_18px.svg) Safari uses the displayName property as the symbol name in Error stack traces.** 209 | 210 | #### Programatically capturing stack traces 211 | 212 | If an error is reported without a stack trace (see more details when this would happen below), then it's possible to programatically capture a stack trace. 213 | 214 | In Chrome, this is really easy to do by using the `Error.captureStackTrace` API. See https://github.com/v8/v8/wiki/Stack%20Trace%20API for more information on the use of this API. 215 | 216 | For example: 217 | 218 | ```javascript 219 | function ignoreThisFunctionInStackTrace() { 220 | var err = new Error(); 221 | Error.captureStackTrace(err, ignoreThisFunctionInStackTrace); 222 | return err.stack; 223 | } 224 | ``` 225 | 226 | In other browsers, a stack trace can also be collected by creating a new error and accessing the stack property of that object: 227 | 228 | ```javascript 229 | var err = new Error(''); 230 | return err.stack; 231 | ``` 232 | 233 | However, IE10 only populates the stack trace when the error is actually thrown: 234 | 235 | ```javascript 236 | try { 237 | throw new Error(''); 238 | } catch (e) { 239 | return e.stack; 240 | } 241 | ``` 242 | 243 | If none of these approaches work, then it's possible to create a rough stack trace without line numbers or columns by iterating over the `arguments.callee.caller` object - this won't work in ES5 Strict Mode though and it's not a recommended approach. 244 | 245 | #### Async stack traces 246 | 247 | It is very common for asynchronous points to be inserted into JavaScript code, such as when code uses `setTimeout` or through the use of Promises. These async entry points can cause problems for stack traces, since they cause a new execution context to form and the stack trace starts from scratch again. 248 | 249 | Chrome DevTools has support for async stack traces, or in other words making sure the stack trace of an error also shows the frames that happened before the async point was introduced. With the use of setTimeout, this will capture who called the setTimeout function that eventually produced an error. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ for more information. 250 | 251 | An async stack trace will look like: 252 | 253 | ``` 254 | throwError @ throw-error.js:2 255 | setTimeout (async) 256 | throwErrorAsync @ throw-error.js:10 257 | (anonymous function) @ throw-error-basic.html:14 258 | ``` 259 | 260 | Async stack traces are only supported in Chrome DevTools right now, only for exceptions that are thrown when DevTools are open. Stack traces accessed from Error objects in code will **not** have the async stack trace as part of it. 261 | 262 | It is possible to polyfill async stack traces in some cases, but this could cause a significant performance hit for your application since capturing a stack trace is not cheap. 263 | 264 | **![Only Chrome supports async stack traces](https://mknichel.github.io/javascript-errors/ic_warning_black_18px.svg) Only Chrome DevTools natively supports async stack traces.** 265 | 266 | #### Naming inline scripts and eval 267 | 268 | Stack traces for code that was eval'ed or inlined into a HTML page will use the page's URL and line/column numbers for the executed code. 269 | 270 | For example: 271 | 272 | ``` 273 | at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) 274 | at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3 275 | ``` 276 | 277 | If these scripts actually come from a script that was inlined for optimization reasons, then the URL, line, and column numbers will be wrong. To work around this problem, Chrome and Firefox support the `//# sourceURL=` annotation (Safari, Edge, and IE do not). The URL specified in this annotation will be used as the URL for all stack traces, and the line and column number will be computed relative to the start of the `