Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting the most out of JavaScript errors

Getting the most out of JavaScript errors

benvinegar

June 06, 2019
Tweet

More Decks by benvinegar

Other Decks in Programming

Transcript

  1. Getting the most out of JavaScript errors Build better apps

    by being better informed when they break
  2. In this talk … • The Error object • Custom

    Errors • The Stack property • Throwing non-Errors • Reporting Errors & CORS
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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');
  11. 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); } }
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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 {
  17. 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
  18. 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
  19. 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
  20. Raising non-errors in Python $ python -c "raise 'lol'" 


    Traceback (most recent call last): File "<string>", line 1, in <module> TypeError: exceptions must be old-style classes or derived from BaseException, not str
  21. 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
  22. Promise rejections let promise = new Promise((resolve, reject) => {

    setTimeout(() => { reject("oops"); }, 100); }); promise.catch((err) => { console.log(err); }); ❌ oops 1 1
  23. 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);
  24. 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
  25. 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
  26. 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 }) }); }
  27. 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 }) });
 };
  28. 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
  29. onerror cont’d <script src=“http://cdn.example.com/example.js”></script> <script> window.onerror = function (msg, url,

    line, col, err) { console.log(msg, url, line, col, err); } </script> // cdn.example.com/example.js doesNotExist(); // throws ❌
  30. 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)
  31. 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
  32. 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 <script crossorigin="anonymous" src=“http://cdn.example.com/example.js”></script> <script> window.onerror = function (msg, url, line, col, err) { console.log(msg, url, line, col, err); } </script> // cdn.example.com/example.js doesNotExist(); // throws
  33. 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
  34. 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