Error Handling 101

const ME = Object.freeze({
  name: 'Sergey Bolshov',
  position: 'Senior Front-end Software Engineer',
  workPlace: 'Allegro',
  email 'bolszow@gmail.com',
  githubUsername: '@bigsergey',
  from: 'Kazakhstan'
});

Who am I?

What every programmer must do before he dies?

Break down production

(even once in a professional career)

Yes

The 5th Edition of the Software Fail Watch in 2017 identified:

  • impacting 3.7 billion people,
  • $1.7 trillion in assets,
  • 606 recorded software failures,
  • 314 companies.

No

If debugging is the process of removing software bugs, then programming must be the process of putting them in.

Edsger Dijkstra

Source: goodreads.com

Source: TRICENTIS

Are errors bad?

What is an error?

An error (from the Latin error, meaning "wandering")

is an action which is inaccurate or incorrect. In some usages, an error is synonymous with a mistake. In statistics, "error" refers to the difference between the value which has been computed and the correct value. An error could result in failure or in a deviation from the intended performance or behaviour.

Source: https://en.m.wikipedia.org/wiki/Error

Is it GOOD definition?

Error

is constructor which creates an error object.


  const error = new Error('Message');

Error

new Error([message[, fileName[, lineNumber]]]);
  • three optional parameters
// this:
const x = Error('Error without new');
​​​​// has the same functionality as this:
const y = new Error('Error with new');
  • used as a function
class PermissionError extends Error {
  constructor(message) {
    super(message);

    this.name = 'PermissionError';
    this.message = message;
  }
}
  • create custom error

Error.prototype.stack

Not part of any specification. Non-standard!!!

Error: OMG, it does not work!
    at withErrorObject (index.js:7)
    at window.onload (index.js:21)
Error: "OMG, it does not work!"
    withErrorObject webpack:///./src/client/index.js?:7
    onload webpack:///./src/client/index.js?:21

Chrome 75

Firefox 67

// Syntax error
console.log("hello, world!";


















// Syntax error
console.log("hello, world!");

// Runtime error
const user = {
  firstName: 'Sergey',
  lastName: 'Bolshov',
  city: 'Poznań'
};

user.getFullName(); // TypeError: user.getFullName is not a function







// Syntax error
console.log("hello, world!");

// Runtime error
const user = {
  firstName: 'Sergey',
  lastName: 'Bolshov',
  city: 'Poznań'
};

user.getFullName(); // TypeError: user.getFullName is not a function

// Logical error
user.getFullName = function () { return this.city; };
user.getFullName(); // Poznań





Type of errors

Error types

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

  • EvalError

const pi = 3.14159;
pi.toFixed(100000); // RangeError

function foo() {
  bar++;  // ReferenceError
}

if (foo) {  // SyntaxError
  // the closing curly brace is missing

var foo = {};

foo.bar(); // TypeError

decodeURIComponent("%"); // URIError

The throw statement

function withoutErrorObject() {
  throw "OMG, it does not work!";
}

window.onload = () => withoutErrorObject();

// Uncaught OMG, it does not work!


function withErrorObject() {
  throw new Error("OMG, it doea not work!");
}

window.onload = () => withErrorObject();

/**
 * Uncaught Error: OMG, it does not work!
 *  at withErrorObject (index.js:6)
 *  at onload (index.js:10)
 */

throws a user-defined exception

The throw statement

function withoutErrorObject() {
  throw "OMG, it does not work!";
}

window.onload = () => withoutErrorObject();

// Uncaught OMG, it does not work!


function withErrorObject() {
  throw new Error("OMG, it does not work!");
}

window.onload = () => withErrorObject();

/**
 * Uncaught Error: OMG, it does not work!
 *  at withErrorObject (index.js:6)
 *  at onload (index.js:10)
 */

Tip: ESLint rule no-throw-literal

try ... catch ... finally

try ... catch ... finally

try {
  // Code to be executed
} 
catch (error) {
  // Code that are executed if an exception 
  // is thrown in the try block
} 
finally {
  // Code that are executed regardless of whether 
  // an exception was thrown or caught
}

Usage example

function loadData(json) {
  let data;

  startLoadAnimation();

  try {
    data = JSON.parse(json); //throws when json has syntax error
    if (!data.isValid) {
      throw new Error('Data is not valid!');
    }
  } catch (error) {
    showInvalidDataMessage(error);
  } finally {
    stopLoadAnimation();
  }

  return data;
}
function loadData(json) {
  let data;

  startLoadAnimation();

  try {
    data = JSON.parse(json); //throws when json has syntax error
    if (!data.isValid) {
      throw new Error('Data is not valid!');
    }
  } catch (error) {
    showInvalidDataMessage(error);
  } finally {
    stopLoadAnimation();
  }

  return data;
}
function loadData(json) {
  let data;

  startLoadAnimation();

  try {
    data = JSON.parse(json); //throws when json has syntax error
    if (!data.isValid) {
      throw new Error('Data is not valid!');
    }
  } catch (error) {
    showInvalidDataMessage(error);
  } finally {
    stopLoadAnimation();
  }

  return data;
}
function loadData(json) {
  let data;

  startLoadAnimation();

  try {
    data = JSON.parse(json); //throws when json has syntax error
    if (!data.isValid) {
      throw new Error('Data is not valid!');
    }
  } catch (error) {
    showInvalidDataMessage(error);
  } finally {
    stopLoadAnimation();
  }

  return data;
}
function loadData(json) {
  let data;

  startLoadAnimation();

  try {
    data = JSON.parse(json); //throws when json has syntax error
    if (!data.isValid) {
      throw new Error('Data is not valid!');
    }
  } catch (error) {
    showInvalidDataMessage(error);
  } finally {
    stopLoadAnimation();
  }

  return data;
}

Async error handling

somePromise().then(function () {
  throw new Error('oh noes');
}).catch(function (err) {
  // I caught your error! :)
});

somePromise().then(function () {
  throw new Error('oh noes');
}, function (err) {
  // I didn't catch your error! :(
});

Async error handling

async function getData(userId, orderId) {
  try {
    const user = await getUser(userId);
    const order = await getOrder(orderId);
  } catch (error) {
    // catch errors both in getUser and getOrder
    console.error(error);
  }
}

Async error handling

function asyncFunction() {
  try {
   setTimeout(() => {
    throw new Error("I'am not gonna be caught!");
   }, 1);
  } catch (error) {
    // it will never enter here
  }
}

Async error handling

function asyncFunction() {
  setTimeout(() => {
   try {
    throw new Error("I'am not gonna be caught!");
   } catch (error) {
    // handle error
   }
  }, 1);
}

Bad handler

function badHandler(fn) {
  try {
    return fn();
  } catch (e) {
    return null;
  }
}
  • silent fail
  • difficult debugging

Ugly handler

function uglyHandler(fn) {
  try {
    return fn();
  } catch (e) {
    throw new Error('Function call fails!!!');
  }
}
  • we know something goes wrong
  • the exception gets bubbled through the call stack
  • better debugging
  • but we lose the original error

Handler with custom error

class SpecifiedError extends Error {
  constructor(message) {
    super(message);

    this.name = 'SpecifiedError';
    this.message = message;
  }
}

function uglyHandlerImproved(fn) {
  try {
    return fn();
  } catch (e) {
    throw new SpecifiedError(e.message);
  }
}

Rethrow error

class PermissionError extends Error {
  constructor(message) {
    super(message);

    this.name = 'PermissionError';
    this.message = message;
  }
}

try {
  // assume an exception occurs
} catch (error) {
  if (error instanceof PermissionError) {
    // Handle PermissionError exceptions
  } else {
    throw error;
  }
}

window.onerror

window.onerror = function (message, source, lineno, columnno, error) {
  // ... handle error ...

  return false;
}

window.addEventListener('error', function (event) {
  // ... handle error ...
  const { error, message, filename, colno, lineno } = event;
  const { message, stack } = error;
});

“Script error” is what browsers send to the onerror callback when an error originates from a JavaScript file served from a different origin (different domain, port, or protocol).

<script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>

window.onerror

What we know already?

  • How to create error
  • How to throw error
  • How to catch error

How do we know that user spots an error?

JavaScript error reporting/monitoring tools

helps you identify production errors impacting your users' experiences and fix bugs before users report them.

Source: Sentry.io

JavaScript error reporting/monitoring tools

JavaScript error reporting/monitoring tools

  • Easy to use

  • Enhanced stack trace
  • Dashboards with:
    • Percentage of page views with errors
    • Group errors by browser
  • Source map support
  • Ignore selected errors

  • ...

Initialization is easy

(here and later Sentry as example)

// When using npm, import Sentry
import * as Sentry from '@sentry/browser';

Sentry.init({ 
  dsn: 'https://<key>@sentry.io/<project>' 
  sampleRate: 0.1, // only 10% of events will be sent
});
npm install @sentry/browser
<script 
  src="https://browser.sentry-cdn.com/5.4.3/bundle.min.js" 
  crossorigin="anonymous"
></script>

or

init the Sentry Browser SDK as soon as possible during your page load

Source maps

function withErrorObject() {
  throw new Error("OMG, it does not work!");
}

window.onload = () => withErrorObject();

/**
 * Uncaught Error: OMG, it does not work!
 *  at withErrorObject (index.js:6)
 *  at onload (index.js:10)
 */
!function(e,t){"use strict";!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else{var o=t();for(var n in o)("object"==typeof exports?exports:e)[n]=o[n]}}(t,function(){return function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}return o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="https://assets.allegrostatic.com/opbox-navigation-tiles/",o(o.s=0)}({0:function(e,t,o){e.exports=o("O14P")},O14P:function(e,o){t.onload=function(){return function(){throw Error("OMG, it does not work!")}()}}})})}(0,"undefined"!=typeof window?window:global);

/**
 * Uncaught Error: OMG, it does not work!
 *  at index_ecb63409.js:1
 *  at O14P.t.onload (index_ecb63409.js:1)
 */

Source maps using Webpack

npm install --save-dev @sentry/webpack-plugin
// webpack.config.js

const SentryWebpackPlugin = require('@sentry/webpack-plugin');

module.exports = {
  // other configuration
  plugins: [
    new SentryWebpackPlugin({
      include: '.',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties'
    })
  ]
};
sentry-cli releases files VERSION upload-sourcemaps /path/to/sourcemaps

Ignoring errors from third-party scripts and unsupported browsers

Sentry.init({ 
  dsn: 'https://<key>@sentry.io/<project>',
  whitelistUrls: [
    'www.example.com/static/js', // your code
    'ajax.googleapis.com'        // code served from Google CDN
  ],
});
  • Legacy browsers
  • Web crawlers
  • Browser extensions

Use inbound data filters:

Error agregation issues

  • Different browsers
  • Different localizations
// Error IE: Obiekt nie obsługuje właściwości lub metody „values”

// Error Safari: Object.values is not a function.(In 'Object.values(r)', 'Object.values' is undefined)

// Error Chrome: Object.values is not a function

Enriching error data

Anything that can go wrong will go wrong

Murphy's law

Thank YOU!

Resources:

What does "101" mean?

It means "introductory something".

The allusion is to a college course with the course code 101, which in the American system and probably others indicates an introductory course, often with no prerequisites.

Source: english.stackexchange.com

Error Handling 101

By Sergey Bolshov

Error Handling 101

  • 1,219