Skip to content

Functions

A good function typically takes one or only a few parameters and returns an output. If you’re writing functions that need many many parameters, it might be a sign to split it up into smaller functions. Likewise if you can’t fit the code for the function on a single page of your editor: it probably ought to be made more digestable.

Two of the common ways you’ll see a function expressed in Javascript:

function blah() {
...
}
const blah = () => { ...
}

The first (somewhat old-school) style has the occasional benefit that you can call a function before it shows up in the source code.

// This case works
blah();
function blah(blah) { ... }
// This case does not work
blah();
const blah = () => { ... }

The demos mostly prefer the second form, the so-called ‘fat arrow’ syntax. One reason is that it’s more succinct, and when the body of the function is a simple expression, you can do away with the { } and return as well:

const doubleValue = v => v*2;
// Same as these two
const doubleValue = (v) => { return v*2 }
function doubleValue(v) { return v*2 }

When using functions as parameters to other functions, the preferred syntax is less cluttered, allowing you to see what’s going on more clearly.

// So smooth....
someArray.filter(v => v.startsWith(`a`));
// So harsh...
someArray.filter(function(v) {
return v.startsWith(`a`);
})

There are some other advantages and disadvantages of the two function styles, but for the context of the ixfx demos, these don’t matter.

Parameters

When making a function, it probably ought to be taking some input.

const greet = (name) => `Hello ${name}!`;
greet(`John`); // `Hello John!`

Using type annotations, we can also suggest what kind of data the parameter should be. This helps when calling and writing the function, because your code editor will give you helpful warnings.

/**
* @param {string} name
**/
const greet = (name) => `Hello ${name}!`;
greet(10); // Warning in the editor

Multiple parameters can be listed with a comma, mirroring how they get passed to the function.

/**
* @param {string} name
* @param {number} size
**/
const greet = (name, size) => `Hello ${name}! Your chosen size is ${size}`;
greet(`Yoko`, 51); // `Hello Yoko! Your chosen size is 51`

Parameters can also have default values. This makes it easier from the caller side, since it doesn’t have to provide values it doesn’t really ‘care’ about. Note the [ ] around the parameter name in the type annotation to denote optionality.

/**
* @param {string} name
* @param {number} [size]
**/
const greet = (name, size = 12) => `Hello ${name}! Your chosen size is ${size}`;
greet(`Yoko`); // `Hello Yoko! Your chosen size is 12`

A key rule about default parameters is they have to be on the far-right on the list of parameters. This won’t work as expected (and indeed VS Code will warn you about it):

const greet = (name=`Julian`, size) => `Hello ${name}! Your chosen size is ${size}`;
greet(12); // `Hello 12! Your chosen size is undefined`

Returning objects

One ‘gotcha’ with the fat arrow function style is if you return an object, it has to be enclosed in ( ). That’s because the the { } we need to define an object is ambiguous with the start and end of a function block.

// This is fine, just returning a simple value like a string
const generate = () => `John`;
// Won't work, because { } could also mean the body of a function in this context
const generate = () => { name: `John`, size: 123 };
// So we need to wrap it:
const generate = () => ({ name: `John`, size: 123 });

This is not a problem with the old-school approach because the semantics are not ambigious. However, it remains more cluttered:

function generate() {
return { name: `John`, size: 123 }
}

Returning functions

In Javascript and ixfx, it’s common for functions to return functions. This can certainly be hard to wrap your head around at times.

// Return a function that returns an object { name, size }
const generate = (name) => () => ({ name, size: Math.random() })
...
// 'g' is essentially () => ({ name: `John`, size: Math.random() })
const g = generate(`John`);
const value = g(); // { name: `John`, size: 0.2841 }

This technique is useful because we can ‘bake in’ parameters without having to pass them in each and every time.

A perfect example is interpolate. Let’s say we always want to interpolate values at the same rate. We’d have to repeat this parameter continually, or add it to something like settings:

// Interpolate between 10-20 by 20%
// uses the signature (amount:number, a:number, b:number) => number
const value = interpolate(0.2, 10, 20);

One of the ways of calling interpolate lets us bake in the amount:

// Returns a function (a:number, b:number) => number
const slow = interpolate(0.2);
// Interpolate between 10-20 by 20%
slow(10, 20);

This can be useful for DRY principles, but also the idea of encapsulation. Now we have a function that can interpolate with two inputs, which can be shared with other parts of your code without them needing to know how the interpolation happens. Only the code that creates the interpolation function needs to decide.

Maybe we set up our interpolate function once, as a setting, so it’s a reusable function later. In turn, this makes update() simpler and more readable.

const settings = Object.freeze({
angleInterpolate: interpolate(0.2)
})
const update = () => {
const { angleInterpolate } = settings;
let { angleCurrent, angleTarget } = state;
// Compute new current angle based on existing and target,
// using the interpolator from `settings`.
saveState({
angleCurrent: angleInterpolate(angleCurrent, angleTarget)
// Instead of:
// angleCurrent: interpolate(0.2, angleCurrent, angleTarget)
})
}

We could imagine a refactoring where angleInterpolate instead computes a random value between the a and b values. The neat thing is we wouldn’t need to change update() at all, since angleInterpolate still has the same signature of (number, number) => number.

const settings = Object.freeze({
angleInterpolate: (a, b) => Random.float({ min:a, max:b })
})

If you find yourself calling a function repeatedly with the same parameters, you might want to write a little function that wraps it and passes in the standard parameter.

For example, you might have lots of:

someElement.addEventListener(`click`, event => {
// do some stuff
})

A little helper function could be:

const onClick = (el, func) => {
el.addEventListener(`click`, func);
}

Which you can then then use with:

onClick(someElement, event => {
// do some stuff
});

If it ends up saving you some typing and making your code more readable, it might be worth it, even for such trivial cases.

If you end up with a few of these, can stash them away in a separate file to be imported and shared:

util.js
export const onClick = (el, func) => {
el.addEventListener(`click`, func);
}
script.js
import { onClick } from './util.js'
onClick(someElement, event => {
// do some stuff
})

On objects

Functions can ‘hang’ off objects. This is a great way of grouping functions together.

const util = {
doX: () => {
...
},
doY: () => {
...
}
}
util.doX();

This is essentially what happens when we import as a module:

util.js
export const doX = () => { ... }
export const doY = () => { ... }
script.js
import * as util from "./util.js"
util.doX();