Getting the most out of JavaScript errors

Getting the most out of JavaScript errors

2ab326462da24e47db9a12eb984524aa?s=128

benvinegar

June 06, 2019
Tweet

Transcript

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

    by being better informed when they break
  2. None
  3. None
  4. None
  5. import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: 'https://abc123@sentry.io/1337' });

    throw new Error('lol'); // Error event is captured
  6. None
  7. Uncaught [object Object]

  8. *no stack trace*

  9. “Script error”

  10. In this talk … • The Error object • Custom

    Errors • The Stack property • Throwing non-Errors • Reporting Errors & CORS
  11. The Error object

  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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');
  20. 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); } }
  21. Error.prototype.stack

  22. 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
  23. 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
  24. 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
  25. 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
  26. 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 {
  27. 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
  28. ⚠ Error.prototype.stack

  29. 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
  30. 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
  31. Non-Error exceptions

  32. 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
  33. 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
  34. Promise rejections let promise = new Promise((resolve, reject) => {

    setTimeout(() => { reject("oops"); }, 100); }); promise.catch((err) => { console.log(err); }); ❌ oops 1 1
  35. 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);
  36. None
  37. 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
  38. 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
  39. Reporting Errors

  40. 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 }) }); }
  41. 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 }) });
 };
  42. 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
  43. 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 ❌
  44. 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)
  45. <script crossorigin="anonymous" src=“http://cdn.example.com/example.js”> </script> 1. Set crossorigin=“anonymous”

  46. 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
  47. 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
  48. Wrapping up …

  49. 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
  50. None
  51. None
  52. None
  53. 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