Functions

Write functions with clear inputs and one job, and know which form to use.

08of 32
≈ 12Min read
8Parts

Three ways to write one

  • A declaration is hoisted completely, which lets you put helpers below the code that uses them.
  • An arrow with no braces returns its expression. With braces you must write return.
  • To return an object literal from a concise arrow, parenthesise it: () => ({ ok: true }).
and what differs
// declaration — hoisted, so it can be called above where it is written
function area(w, h) {
  return w * h;
}

// expression — not hoisted; exists from this line on
const area = function (w, h) {
  return w * h;
};

// arrow — not hoisted, no own `this`, and concise
const area = (w, h) => w * h;

Parameters

defaults, rest, and an options object
function tag(name, attrs = {}, ...children) {
  // attrs defaults only when undefined is passed — null does NOT trigger it
}

tag("div");                    // attrs is {}
tag("div", null);              // attrs is null

// a default can use an earlier parameter
function slice(list, start = 0, end = list.length) { }

// more than three parameters: take an object instead
function request(url, { method = "GET", retries = 3, signal } = {}) { }
request("/api", { retries: 1 });
Why an options object

Nobody can remember the order of five arguments, and request(url, null, undefined, true) is unreadable. Named options can also be extended without breaking every call.

Returning

and the one that silently breaks
function noReturn() { }
noReturn();             // undefined

// automatic semicolon insertion puts a ; after return
function broken() {
  return
    { ok: true };       // returns undefined!
}

function fixed() {
  return {
    ok: true,
  };
}

Arrow functions and this

the reason they exist
const timer = {
  seconds: 0,

  start() {
    // an arrow keeps the surrounding `this`, which is the timer
    setInterval(() => { this.seconds += 1; }, 1000);

    // a function expression would get its own `this` — undefined here
    setInterval(function () { this.seconds += 1; }, 1000);   // TypeError
  },
};
So do not use an arrow as a method

const obj = { name: "a", get: () => this.name } does not work: the arrow took this from the module, not from obj.

Pure functions

Most code has to do something impure eventually — write to the DOM, save a file, send a request. The useful discipline is to keep the calculation pure and put the side effect in one obvious place.

same input, same output, no side effects
// pure: easy to test, safe to move, safe to call twice
const withVat = (minor, rate) => Math.round(minor * (1 + rate / 100));

// impure: reads outside state and changes its argument
let rate = 18;
function addVat(order) {
  order.total = Math.round(order.total * (1 + rate / 100));
}

One job

Each of those can be tested on its own, and the one that changes next month can change without touching the others.

a function that does three things is three functions
// before
function save(form) {
  const data = Object.fromEntries(new FormData(form));
  if (!data.email.includes("@")) { showError("Bad email"); return; }
  data.total = data.qty * 100;
  fetch("/api", { method: "POST", body: JSON.stringify(data) });
}

// after
const read = (form) => Object.fromEntries(new FormData(form));
const validate = (data) => (data.email.includes("@") ? null : "Bad email");
const priced = (data) => ({ ...data, total: data.qty * 100 });
const send = (data) => fetch("/api", { method: "POST", body: JSON.stringify(data) });

Functions as values

pass them, return them, store them
const by = (key) => (a, b) => a[key] - b[key];
doors.sort(by("days"));

const handlers = {
  save: () => { },
  cancel: () => { },
};
handlers[action]?.();

// a function that returns a function is how you make a configurable one
const prefix = (p) => (s) => `${p}${s}`;
const mm = prefix("↔ ");

Naming

  • A verb: formatPrice, loadDoors, isValid, hasError.
  • A predicate returns a boolean and reads as a question: isReady(x).
  • A getter returns a value and does nothing else: priceOf(x).
  • If the name needs "And" in it — validateAndSave — it is two functions.

Practice