Skip to content

JavaScript

Custom Error subclasses

You can use error subclassing to define new properties that are used as metadata on the error. Here is an example of an HTTPError that includes status, statusText, and url properties to construct a custom error message:

js
class HTTPError extends Error {
  status;
  statusText;
  url;

  constructor(status, statusText, url) {
    super(`${status} ${statusText}: ${url}`);
    this.status = status;
    this.statusText = statusText;
    this.url = url;
  }

  get name() {
    return "HTTPError";
  }
}

Usage looks like this:

js
const url = new URL(/*...*/);
const response = await fetch(url);
if (!response.ok) {
  const error = new HTTPError(response.status, response.statusText, url.toString());

  error instanceof HTTPError; // true
  error.message; // e.g. '404 Not Found: https://example.com'
  error.name; // 'HTTPError'
}

Be explicit with the callbacks you pass to Array and Object methods

Credit: https://jakearchibald.com/2021/function-callback-risks/

In general, don't pass a named function as the callback of Array and Object methods unless it was specifically designed for it.

js
// DO NOT do this
arr.map(parseInt);

// Do this instead
arr.map((n) => parseInt(n));

The reason is because we usually see the first example and mentally apply the callback function only to the element of each array (that is, the two examples above look the same at a glance). But most Array and Object methods invoke the callback with multiple arguments, and sometimes the callback function accepts multiple arguments. This can result in unexpected behavior.

js
["1", "2", "3"].map(parseInt); //=> [ 1, NaN, NaN ]
["1", "2", "3"].map((n) => parseInt(n)); //=> [ 1, 2, 3 ]

How to abort fetch and other Promises

Aborting fetch

Abort a fetch using an AbortController.

js
const context = {};

async function fetchVideo() {
  context.controller = new AbortController();

  try {
    const response = await fetch("/video.mp4", {
      signal: context.controller.signal,
    });
    console.log("Download complete", response);
  } catch (error) {
    // if the fetch is aborted, there will be an abort error here.
    console.error(`Download error: ${err.message}`);
  }
}

document.querySelector("#cancel-button").addEventListener("click", () => {
  context.controller?.abort();
});

document.querySelector("#download-button").addEventListener("click", fetchVideo);

Aborting any Promise

We can also use AbortSignal to early-reject a Promise.

ts
/**
 * Use this to pair any `Promise` with an `AbortSignal`. You can call the
 * `abort()` method on your controller to reject the Promise early.
 */
export function cancellable<T>(wrappedPromise: Promise<T>, signal: AbortSignal) {
  return new Promise((resolve, reject) => {
    signal.addEventListener("abort", () => {
      reject("Promise aborted.");
    });
    wrappedPromise.then(resolve).catch(reject);
  });
}

Here is an example of usage.

js
// Here is a generic async function that resolves after 5 seconds.
function doStuff() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("success!");
    }, 5000);
  });
}

// Create your controller.
const controller = new AbortController();

// In a UI application, you might call `abort()` in response to a user
// action, such as clicking a button. For this example, we will wait
// 2 seconds and then call it. Play around with this timeout to see what
// happens with your cancellable promise!
setTimeout(() => {
  controller.abort();
}, 2000);

try {
  const result = await cancellable(doStuff(), controller.signal);
  console.log({ result });
} catch (error) {
  console.log({ error });
}

console.log("the Promise has settled and we can move on");

Detect the OS from the browser

AFAICT, there is no stable API for looking at the client's platform.

Therefore we should use both to detect the platform.

js
const platform = navigator.userAgentData?.platform ?? navigator.platform ?? "unknown";

const isMac = /mac/i.test(platform);
const isWin = /win/i.test(platform);
const isLinux = /linux/i.test(platform);

This work is licensed under CC BY-NC-ND 4.0