Slide 1

Slide 1 text

Getting the most out of JavaScript errors Build better apps by being better informed when they break

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: 'https://[email protected]/1337' }); throw new Error('lol'); // Error event is captured

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Uncaught [object Object]

Slide 8

Slide 8 text

*no stack trace*

Slide 9

Slide 9 text

“Script error”

Slide 10

Slide 10 text

In this talk … • The Error object • Custom Errors • The Stack property • Throwing non-Errors • Reporting Errors & CORS

Slide 11

Slide 11 text

The Error object

Slide 12

Slide 12 text

new Error([message[, fileName[, lineNumber]]]) “The Error constructor creates an error object. Instances of Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user- defined exceptions.” https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

Slide 13

Slide 13 text

Runtime Errors function c() { d(); // does not exist } function b() { c(); } function a() { b(); } a(); example.js:2 Uncaught ReferenceError: d is not defined at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13

Slide 14

Slide 14 text

User-defined Errors function c() { throw new ReferenceError('d is not defined'); } function b() { c(); } function a() { b(); } a(); example.js:2 Uncaught ReferenceError: d is not defined at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13

Slide 15

Slide 15 text

User-defined Errors function c() { throw new Error('this is super broken'); } function b() { c(); } function a() { b(); } a(); example.js:2 Uncaught Error: this is super broken at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13

Slide 16

Slide 16 text

name message stack example.js:2 Uncaught Error: this is super broken at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13

Slide 17

Slide 17 text

Error properties function c() { try { throw new Error('this is super broken'); } catch (err) { console.log(err.name); console.log(err.message); console.log(err.stack); } } function b() { c(); } function a() { b(); } a(); example.js:5 Error example.js:6 this is super broken example.js:7 Error: this is super broken at c (example.js:3) at b (example.js:12) at a (example.js:16) at example.js:19 1 2 3 1 2 3

Slide 18

Slide 18 text

Standard Errors ReferenceError • dereference an invalid reference RangeError • numeric variable outside valid range InternalError • internal error from JS engine EvalError • error from global eval() SyntaxError • unparseable code TypeError • variable not a valid type URIError • encode/decodeURI passed invalid params

Slide 19

Slide 19 text

Custom Errors example.js:2 Uncaught SuperBrokenError: nuff said at c (example.js:2:11) at b (example.js:6:5) at a (example.js:10:5) at example.js:13:1 class SuperBrokenError extends Error { constructor(...args) { super(...args); this.name = 'SuperBrokenError'; } }; throw new SuperBrokenError('nuff said');

Slide 20

Slide 20 text

Custom Properties class APIError extends Error { constructor(message, status = null) { super(message); this.name = 'APIError'; this.status = status; } }; throw new APIError('Internal Service Error', 500); // ... try { someCode(); } catch (err) { if (err instanceof APIError) { console.log(err.status); } }

Slide 21

Slide 21 text

Error.prototype.stack

Slide 22

Slide 22 text

name message stack example.js:2 Uncaught Error: this is super broken at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13

Slide 23

Slide 23 text

function c() { return new Error(‘this is super broken'); } function b() { return c(); } function a() { return b(); } // a() returns Error instance created in c() throw a(); Stack generation Uncaught Error: this is super broken at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:13 1 1 2 3 2 3 4 4

Slide 24

Slide 24 text

Rethrowing errors function c() { d(); // does not exist } function b() { c(); } function a() { b(); } try { a(); } catch (err) { // do some stuff, then ... throw err; // rethrow same object } Uncaught ReferenceError: d is not defined at c (example.js:2) at b (example.js:6) at a (example.js:10) at example.js:14 1 1 2 3 2 3 4 4

Slide 25

Slide 25 text

Rethrowing errors cont’d function c() { d(); // does not exist } function b() { c(); } function a() { b(); } try { a(); } catch (err) { // do some stuff, then ... throw new CustomError(err.message); } 1 Uncaught CustomError: d is not defined at example.js:17 1

Slide 26

Slide 26 text

Async stacks function c() { setTimeout(function () { try { d(); // does not exist } catch (err) { console.log(err); } }); } function b() { c(); } function a() { b(); } a(); ReferenceError: d is not defined at example.js:4 1 1 {

Slide 27

Slide 27 text

Async w/ Chrome devtools Uncaught ReferenceError: d is not defined at example.js:3 (anonymous) @ example.js:3 setTimeout (async) c @ example.js:2 b @ example.js:8 a @ example.js:12 (anonymous) @ example.js:15 function c() { setTimeout(function () { d(); // does not exist }); } function b() { c(); } function a() { b(); } a(); 1 2 3 4 1 2 3 4 5 5

Slide 28

Slide 28 text

⚠ Error.prototype.stack

Slide 29

Slide 29 text

Error: this is super broken at c (http://localhost:5000/example.js:2:17) at b (http://localhost:5000/example.js:6:5) at a (http://localhost:5000/example.js:10:5) at http://localhost:5000/example.js:13:1 c@http://localhost:5000/example.js:2:17 b@http://localhost:5000/example.js:6:5 a@http://localhost:5000/example.js:10:5 @http://localhost:5000/example.js:13:1 c@http://localhost:5000/example.js:2:20 b@http://localhost:5000/example.js:6:6 a@http://localhost:5000/example.js:10:6 global code@http://localhost:5000/example.js:13:2

Slide 30

Slide 30 text

Error.prototype.stack • Generated at instantiation (i.e. new Error()) • Re-throwing the same error will not generate a new stack • First frame is top-most asynchronous execution • Is just a string • Is non-standard

Slide 31

Slide 31 text

Non-Error exceptions

Slide 32

Slide 32 text

Raising non-errors in Python $ python -c "raise 'lol'" 
 Traceback (most recent call last): File "", line 1, in TypeError: exceptions must be old-style classes or derived from BaseException, not str

Slide 33

Slide 33 text

Raising non-errors in JavaScript function c() { throw "oops"; } function b() { c(); } function a() { b(); } try { a(); } catch (err) { console.log(err); } ❌ oops 1 1

Slide 34

Slide 34 text

Promise rejections let promise = new Promise((resolve, reject) => { setTimeout(() => { reject("oops"); }, 100); }); promise.catch((err) => { console.log(err); }); ❌ oops 1 1

Slide 35

Slide 35 text

Promise rejections Promise.reject("something bad happened"); Promise.reject(5); Promise.reject(); new Promise(function(resolve, reject) { reject("something bad happened"); }); new Promise(function(resolve, reject) { reject(); }); Promise.reject(new Error("something bad happened")); Promise.reject(new TypeError("something bad happened")); new Promise(function(resolve, reject) { reject(new Error("something bad happened")); }); var foo = getUnknownValue(); Promise.reject(foo);

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Synthetic trace for non-errors function c() { throw "oops"; } function b() { c(); } function a() { b(); } try { a(); } catch (err) { if (!(err instanceof Error)) { throw new Error(err); } } Uncaught Error: oops at example.js:17 1 1

Slide 38

Slide 38 text

Non-Error exceptions • Caused when throwing any non-Error • Make determining root cause difficult (no stack trace) • Promises are a common source of them • Can use Error object to add missing context to thrown non- errors

Slide 39

Slide 39 text

Reporting Errors

Slide 40

Slide 40 text

Try/catch + report try { doesNotExist(); // throws } catch (err) { fetch('/internal/error-reporter', { method: 'POST', body: JSON.stringify({ name: err.name, message: err.message, stack: err.stack }) }); }

Slide 41

Slide 41 text

window.onerror window.onerror = function(msg, url, line, col, err) {
 fetch("/internal/error-reporter", { method: "POST", body: JSON.stringify({ name: err.name, message: err.message, stack: err.stack }) });
 };

Slide 42

Slide 42 text

window.onunhandledrejection = function(evt) { const err = evt.reason; fetch("/internal/error-reporter", { method: "POST", body: JSON.stringify({ name: err.name, message: err.message, stack: err.stack }) }); }; window.onunhandledrejection

Slide 43

Slide 43 text

onerror cont’d window.onerror = function (msg, url, line, col, err) { console.log(msg, url, line, col, err); } // cdn.example.com/example.js doesNotExist(); // throws ❌

Slide 44

Slide 44 text

Same-Origin Policy • onerror cannot listen to errors thrown from scripts on other origins (domain, port, protocol) • Error messages could leak session-specific data • Solution: CORS (Cross-Origin Resource Sharing)

Slide 45

Slide 45 text

1. Set crossorigin=“anonymous”

Slide 46

Slide 46 text

2. Set Access-Control-Allow-Header $ curl http://cdn.example.com/example.js -I HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Range Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Tue, 07 May 2019 04:10:15 GMT ETag: W/"be-16a907c2113" Content-Type: application/javascript; charset=UTF-8 Content-Length: 190 Vary: Accept-Encoding Date: Tue, 07 May 2019 16:47:47 GMT Connection: keep-alive

Slide 47

Slide 47 text

Uncaught ReferenceError: doesNotExist is not defined http://cdn.example.com/example.js 1 1 example.js:1 ReferenceError: doesNotExist is not defined at example.js:1 window.onerror = function (msg, url, line, col, err) { console.log(msg, url, line, col, err); } // cdn.example.com/example.js doesNotExist(); // throws

Slide 48

Slide 48 text

Wrapping up …

Slide 49

Slide 49 text

Errors are your friend • Use custom errors to add meaning to failures • Keep in mind how stack traces are generated • Don’t throw strings, objects, or other non-errors • Use global exception handlers to report errors

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Resources • sentry.io/welcome • github.com/getsentry/sentry • github.com/getsentry/sentry-javascript • What the heck is “Script error”?
 https://blog.sentry.io/2016/05/17/what-is-script-error • Capture and report errors w/ window.onerror
 https://blog.sentry.io/2016/01/04/client-javascript-reporting- window-onerror @bentlegen