Skip to content

Elapsed

A common need to to measure how much time has passed since some point in time. In the below example, we print out the time between each click:

let lastClicked = Number.NaN;
document.addEventListener(`click`, () => {
const elapsed = Number.isNaN(lastClicked) ? -1 : Date.now() - lastClicked;
lastClicked = Date.now();
if (elapsed > 0) {
console.log(`Time since last click: ${elapsed}ms`);
} else {
console.log(`First click`);
}
});

It’s an easy enough pattern to implement but one of the annoying things is that you really care about the elapsed time and not the time of the click, so every time you need it you have to calculate it. The Flow module has a few helper functions for common elapsed time scenarios. These are all based on the same principle: you create a function that tracks the elapsed time, and then call it whenever you want to know how much time has passed.

An overview:

Core.elapsedSince yields how much time (in milliseconds) has passed since first invoked. This is the fixed reference point all later invocations are compared to.

import { elapsedSince } from "https://unpkg.com/ixfx"
// A. Start monitoring elapsed time
const s = elapsedSince();
// ...some time later ...
s(); // B. Elapsed time since (A)
// ...some time later ...
s(); // C. Elapsed time since (A)

In an interaction context:

let startedAt = elapsedSince(); // Records time at this point
document.addEventListener(`click`, () => {
// Report elapsed time since sketch started
console.log(`Time since first started: ${startedAt()}ms`);
});

Core.elapsedInterval reports the time from the first initialisation and each subsequent call. Unlike since, there is a not a fixed reference point. It is always comparing to either the initial time or when the callback was last run.

import { elapsedInterval } from "https://unpkg.com/ixfx"
// A. Start monitoring elapsed time
const i = elapsedInterval();
// ...some time later ...
i(); // B. Elapsed time since (A)
// ...some time later ...
i(); // C. Elapsed time since (B)

In an interaction context:

let clickedAt = elapsedInterval();
document.addEventListener(`click`, () => {
console.log(`Time since last click: ${clickedAt()}ms`);
});

In the above example, the first result won’t be correct, since the initial time when elapsedInterval() is called is used. The first log message will thus be the time from the sketch starting and the first click. After that it will be reporting the time between clicks.

Core.elapsedOnce fixes both the start point and a second reference time. After initialisation, it records the time at which the first call happens. This is then given as the value for all future calls.

import { elapsedOnce } from "https://unpkg.com/ixfx"
// A. Start monitoring elapsed time
const o = elapsedOnce();
// ...some time later...
o(); // B. Time since (A). Since it is the first call, we now fix the second reference.
// ...some time later...
o(); // C. Will be same value as earlier (B-A)
// ... some time later...
o(); // D. As above, forever

If you have a sense of how long something should take, you can track progression.

Flow.ofTotal takes an Interval and returns a function that reports back a 0..1 percentage of completion.

import { Flow } from "https://unpkg.com/ixfx"
const timer = Flow.ofTotal({ mins: 1});
// Every time we call timer() we get the progression
// towards the total duration
timer();

Since we could call the function after the duration has elapsed, it may well return a number over 1. To avoid this, clamp the value:

const timer = ofTotal({ mins: 1}, { clampValue: true });

It’s also possible to wrap the value. When the total duration is exceeded, the progression wraps around back to zero. Therefore, an elapsed time of 150% is reported as 0.5, not 1.5.

const timer = ofTotal({ mins: 1}, { wrapValue: true });

In an interaction context:

let click = elapsedInfinity(); // Use this to denote a click hasn't happende
document.addEventListener(`click`, () => {
if (isInfiniteSince(click)) {
// First time there was a click, so we could handle this differently if we wanted to
}
console.log(`Time since first click: ${click()}ms`);
});

You can see the use of Core.elapsedInfinity in the above case. elapsedInfinity is used as a placeholder, useful when you want to set up a variable, but don’t yet have the action to initiate it. Similar to how we might write let lastClicked = 0. When the click happens we