Skip to content

🔮 Helpers for things that happen in the future.

Notifications You must be signed in to change notification settings

nevoland/futurise

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Futurise

🔮 Helpers for things that happen in the future.

Features

  • Event listener utilities that provide cleanup functions
  • Timer helpers with convenient registering and cleanup functions
  • Promise generators that wait for event occurrences
  • Tool to delay function calls with throttling and introspection capabilities

Usage

Everything is exported from the main entry-point through an ES6 module:

import {
  EventEmitter,
  on,
  once,
  timeout,
  interval,
  delay,
  until,
  untilOnline,
  reduceStatusList,
} from "futurise";

Installation

Install with the Node Package Manager:

npm install futurise

Documentation

Documentation is generated here.

Guide

Create an event emitter

Use EventEmitter to create a new event emitter:

import { EventEmitter } from "futurise";

const emitter = new EventEmitter<{
  A: boolean;
  B: number;
}>();

emitter.dispatchEvent("A", true);
emitter.dispatchEvent("B", 4);

It follows the EventTarget interface for adding and removing listeners:

function listener(event: boolean) {
  console.log(`Boolean value: ${event}`);
}

emitter.addEventListener("A", listener);
emitter.removeEventListener("A", listener);

Register a listener

Use on to register a listener on an element or any object that has the addEventListener and removeEventListener methods of the EventTarget interface:

import { on } from "futurise";

const off = on(element, "click", () => console.log("Clicked!"));

It returns a function that, when called, removes the listener from the element:

off();

This is convenient when used with tools that rely on cleanup functions such as the React hook useEffect:

import { on } from "futurise";
import { useEffect } from "preact/hooks";

useEffect(() => on(element, "click", () => console.log("Clicked!")), [element]);

Partial curry

The on register function can be curried by passing only the target and event name:

import { on } from "futurise";

const register = on(element, "click");

const off = register(() => console.log("Clicked!"));
const anotherOff = register(() => console.log("Another message!"));

This is convenient when using with until, a tool that returns a promise that resolves when a specific event is spotted:

import { until, on } from "futurise";

async function untilOnline(request) {
  await until(on(window, "online"));
  return request();
}

Note that futurise exports a tool untilOnline that resolve to true if it waited for the system to become online:

import { untilOnline } from "futurise";

Register once

Similar to on, once registers a listener for a given event on a target only once:

import { once } from "futurise";

// Will execute the callback only once
once(element, "click", () => console.log("Clicked!"));

Set a timer

Use timeout to execute a macro-task after a given delay:

import { timeout } from "futurise";

const cancel = timeout(1000, () => console.log("At least a second elapsed!"));

It returns a function that, when called, cancels the timer if it has not yet elapsed:

cancel();

Like for the on tool, this is convenient when used with tools that rely on cleanup functions such as the React hook useEffect:

import { timeout } from "futurise";
import { useEffect } from "preact/hooks";

useEffect(
  () => timeout(1000, () => console.log("At least a second elapsed!")),
  [],
);

Partial curry

The timeout register function can be curried by passing only the duration. Similar to currying on, it returns a register function enabling several callbacks to be registered and called once the duration elapses. Note that calling timeout immediately sets the timer:

import { timeout } from "futurise";

const register = timeout(1000);

register(() => console.log("At least a second elapsed!"));
register(() => console.log("This is called immediately after!"));
const unregister = register(() =>
  console.log("But not this, because it will be deregistered"),
);
unregister();

// After about one second, the console will show:
// > At least a second elapsed!
// > This is called immediately after!

Unregistering all callbacks clears the timeout if it did not elapse:

import { timeout } from "futurise";

const register = timeout(1000);

const unregister1 = register(() => console.log("At least a second elapsed!"));
const unregister2 = register(() => console.log("Yes, at least!"));

unregister1();
unregister2();

// The timeout is cleared

As for on, this is convenient when used together with until:

import {timeout, until} from "futurise";

async function task() {
  let done = false;
  while (!done) {
    try {
      await action();
      done = true;
    } except (error) {
      console.error(error);
      await until(timeout(1000));
    }
  }
}

Note that futurise exports a tool sleep that combines until and timeout:

import { sleep } from "futurise";

async function task() {
  await sleep(1000);
}

Set an interval

Use interval to repetitively execute a macro-task after a given interval:

import { interval } from "futurise";

const cancel = interval(1000, () => console.log("At least a second elapsed!"));

It returns a function that, when called, cancels the interval:

cancel();

Partial curry

The interval register function can be curried by passing only the duration. Similar to currying on, it returns a register function enabling several callbacks to be registered and called at each interval. Note that calling interval immediately sets the interval:

import { interval } from "futurise";

const register = interval(1000);

register(() => console.log("At least a second elapsed!"));
register(() => console.log("This is called immediately after!"));
const unregister = register(() =>
  console.log("But not this, because it will be deregistered"),
);
unregister();

// After about one second, the console will show:
// > At least a second elapsed!
// > This is called immediately after!

// After about one second later, the console will show:
// > At least a second elapsed!
// > This is called immediately after!

// And so on…

Unregistering all callbacks clears the interval:

import { interval } from "futurise";

const register = interval(1000);

const unregister1 = register(() => console.log("At least a second elapsed!"));
const unregister2 = register(() => console.log("Yes, at least!"));

unregister1();
unregister2();

// The interval is cleared

Delay calls to a function

Delaying the invocation of a callable (also known as "debouncing") can be done using the delay tool:

import { delay } from "futurise";

function doSomething(parameter) {
  heavyTaskWith(parameter);
}

const doSomethingLessOften = delay(1000, doSomething);

doSomethingLessOften(1);
doSomethingLessOften(2);
doSomethingLessOften(3);
doSomethingLessOften(4);

// Only calls `doSomething(4)` after 1 second

Immediate call

By default, the function is called after the specified duration elapsed. The immediate option enables calling the function immediately:

import { delay } from "futurise";

function doSomething(parameter) {
  heavyTaskWith(parameter);
}

const doSomethingLessOften = delay(1000, doSomething, { immediate: true });

// Calls `doSomething(1)` immediately
doSomethingLessOften(1);
doSomethingLessOften(2);
doSomethingLessOften(3);
doSomethingLessOften(4);

// Calls `doSomething(4)` after 1 second

Throttling

By default, the function is delayed until the duration elapsed before a new call is made. The throttle option enables calling the function at most once every period set by the duration:

import { delay, sleep } from "futurise";

function doSomething(parameter) {
  heavyTaskWith(parameter);
}

const doSomethingLessOften = delay(1000, doSomething, { throttle: true });

async function task() {
  doSomethingLessOften(1);
  await sleep(400);
  doSomethingLessOften(2);
  await sleep(400);
  doSomethingLessOften(3);
  await sleep(400); // Calls `doSomething(3)` while it awaits the sleep timer
  doSomethingLessOften(4);
  await sleep(1000); // Calls `doSomething(4)` while it awaits the sleep timer
}

Without the throttling mode, the function would have been called only once with doSomething(4).

Cancel pending call

The function returned by delay has a cancel() method that cancel the pending invocation:

import { delay } from "futurise";

function doSomething(parameter) {
  heavyTaskWith(parameter);
}

const doSomethingLessOften = delay(1000, doSomething);

doSomethingLessOften(1);
doSomethingLessOften.cancel();

// The function is not called

Flush the pending call

The function returned by delay has a flush() method that immediately runs the pending invocation and returns the resulting value:

import { delay } from "futurise";

function doSomething(parameter) {
  // Returns something
  return heavyTaskWith(parameter);
}

const doSomethingLessOften = delay(1000, doSomething);

doSomethingLessOften(1);
// The function is immediately called and its result is returned
const result = doSomethingLessOften.flush();

Getting the last result

The function returned by delay has a result property that is set to the value returned by the function:

import { delay } from "futurise";

function doSomething(parameter) {
  // Returns something
  return heavyTaskWith(parameter);
}

// Setting `immediate: true` to immediately execute the first invocation:
const doSomethingLessOften = delay(1000, doSomething, { immediate: true });

// The function is immediately called
doSomethingLessOften(1);
// The result can be retreived as such
const result = doSomethingLessOften.result;