Skip to content

ryanmorr/blunder

Repository files navigation

blunder

Version Badge License Build Status

A modern client-side JavaScript error handler

Install

Download the CJS, ESM, UMD versions or install via NPM:

npm install @ryanmorr/blunder

Usage

Blunder offers the ability to normalize, enhance, capture, and report JavaScript errors in the browser:

import { monitor, subscribe, dispatch } from '@ryanmorr/blunder';

// Listen for global runtime errors
monitor();

// Subscribe to be notified when an exception is dispatched
subscribe((exception) => {
    // Log the exception to the server
    navigator.sendBeacon('/path/to/error/logger', JSON.stringify(exception));
});

// Manually dispatch an exception
dispatch('An error occurred!');

API

Exception(message?, options?)

Exception is a subclass of Error to help normalize and enhance error instances:

import { Exception } from '@ryanmorr/blunder';

// Create an exception just like a regular error
const exception = new Exception('error message');

// Normalizes the standard properties
exception.name;
exception.message;
exception.stack;

Add custom metadata as an optional second argument to help identify the error:

const exception = new Exception('error message', {
    timestamp: Date.now(),
    url: document.location.href,
    userAgent: navigator.userAgent
});

// The metadata is stored within the `data` property
exception.data.timestamp;
exception.data.url;
exception.data.userAgent;

Supports the cause property to facilitate error chaining:

try {
    connect();
} catch(error) {
    throw new Exception('Connection failed', {cause: error});
}

Extend Exception to create your own custom subclasses:

class APIException extends Exception {
    constructor(code, options){
        super('error code: ' + code, options);
        this.code = code;
    }
}

Includes a toJSON method that returns a key/value object representing all the properties of the exception. It will convert some non-JSON friendly values to friendly ones (such as functions) and will detect and avoid circular references:

function trySomething() {
    try {
        throw new Exception('error message', {source: trySomething});
    } catch(exception) {
        const serialized = exception.toJSON();
        serialized.data.source; //=> "[Function: trySomething]"
    }
}

You can override the serializable method in subclasses to customize how an instance is serialized:

class MyException extends Exception {
    serializable() {
        return `${this.name}: ${this.message}`;
    }
}

const exception = new MyException('An error occurred');
JSON.stringify(exception); //=> "MyException: An error occurred"

Exception also has a static from method that can be used to convert a value into an Exception instance:

// Supports error messages as strings
const exception = Exception.from('error message');

// Supports normal error instances (will copy standard properties)
const error = new Error();
const exception = Exception.from(error);

// Supports creating instances of subclasses
const exception = CustomException.from('error message');
exception instanceof CustomException; //=> true

subscribe(callback)

Subscribe a callback function to be invoked with an Exception instance when an error is dispatched, it returns an unsubscribe function:

import { subscribe } from '@ryanmorr/blunder';

const unsubscribe = subscribe((exception) => {
    // An exception was dispatched
});

// Unsubscribe from future error notifications
unsubscribe();

dispatch(error, options?)

Manually dispatch an Exception to the error subscribers and return the Exception instance:

import { dispatch } from '@ryanmorr/blunder';

// Supports strings
const exception = dispatch('error message');

// Supports normal error instances
dispatch(new Error());

// Supports custom metadata as an optional second argument
dispatch('error message', {foo: 1, bar: 2});

// Supports `cause` property
try {
    trySomething();
} catch(error) {
    dispatch('error message', {cause: error});
}

monitor(config?)

Enable global error monitoring by listening for the error, unhandledrejection, and rejectionhandled events. You can customize which events to listen for by providing a configuration object. It returns a function to disable monitoring:

import { monitor } from '@ryanmorr/blunder';

// Listen for all error events by default
const stop = monitor();

// Disable error monitoring
stop();

// Optionally specify which events to listen for
monitor({
    error: true,
    unhandledrejection: true,
    rejectionhandled: false
});

attempt(fn, options?)

Internally wraps a function in a try/catch block for easy error handling. If an error is raised, it is automatically converted to an Exception instance, dispatched to error subscribers, and then returned:

import { attempt } from '@ryanmorr/blunder';

const [result, exception] = attempt(() => {
    throw new Error();
});

// If `exception` is defined, the attempt failed
if (exception) {
    console.error(exception);
}

// If `result` is defined, the attempt was successful
if (result) {
    console.log(result);
}

Supports custom metadata as an optional second argument that will be added to the Exception instance if an error should arise:

const [result, exception] = attempt(callback, {
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight
});

Supports async functionality via promises by utilizing async/await syntax:

// Use promises directly
const [result, exception] = await attempt(fetch('/path/to/resource'));

// Use a function that returns a promise
const [result, exception] = await attempt(() => fetch('/path/to/resource'));

License

This project is dedicated to the public domain as described by the Unlicense.