Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
"use strict";
const Promise = require("bluebird");
const createEventEmitter = require("create-event-emitter");
const ms = require("ms");
This timer is meant for usecases where eg. you want to display a particular "loading" message after a certain amount of loading time, but *when* the loading message is displayed it must be visible for at least a certain amount of time, to prevent a visually jarring experience.
This is accomplished by employing two timing periods, for example:
0ms 100ms 200ms
|------------------------|-------------------------|------ --- --- -- -- - -
Activation delay, 100ms Completion delay, 200ms
\_______________________/│\_______________________/│\_____ ___ ___ __ __ _ _
No waiting │ Waiting occurs here │ No waiting
│ │
│ └ Waits until here
│ (where applicable)
└ "activated" event fires here
After the 'activation delay' expires, an 'activated' event is emitted that can be used to know when to eg. display a loading message. The 'completion delay' is used to determine how long to wait before switching to the next view.
Crucially, the behaviour of the `await` method varies depending on whether the timer is in the activation-delay or completion-delay phase; while in the activation-delay phase, `await` will resolve immediately (as no loading UI has been shown yet), whereas in the completion-delay phase, it is only resolved after the completion delay has finished (or immediately, if `await` is called *after* already finishing the completion delay).
To recap:
- Between 0ms and 100ms, `await` will resolve immediately.
- Between 100ms and 200ms, `await` will resolve after (200ms - passedTime) milliseconds.
- After 200ms, `await` will resolve immediately.
This means that it's safe to wire up a view change to the `await` Promise; it will only wait if there's a completion delay to wait out, and otherwise the view change will happen instantly.
Once your loading process has completed, you should call the `disregard` method on the timer; this will prevent the 'activated' event from firing if you were still in the activation-delay phase when the loading completed.
module.exports = function createStaggeredDelayTimer(activationDelay, completionDelay, baseTime = {
let doneAt = baseTime + ms(completionDelay);
let activated = false;
let disregarded = false;
let emitter = createEventEmitter({
await: function awaitStaggeredDelayTimer() {
return Promise.try(() => {
if (activated === true && doneAt > {
return Promise.delay(doneAt -;
disregard: function disregardStaggeredDelayTimer() {
disregarded = true;
setTimeout(() => {
if (disregarded === false) {
activated = true;
}, ms(activationDelay));
return emitter;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.