Skip to content

Function Utilities ​

Functions for controlling function execution and performance optimization.

compose ​

Composes multiple functions from right to left. The output of each function is passed as input to the previous function.

Signature ​

typescript
function compose<T>(...fns: Array<(arg: T) => T>): (arg: T) => T;

Parameters ​

  • fns - Functions to compose (executed right to left)

Returns ​

A new function that represents the composition of the input functions.

Example ​

typescript
import { compose } from "jalutils/function";

// Simple transformations
const addTen = (x: number) => x + 10;
const multiplyByTwo = (x: number) => x * 2;
const subtractFive = (x: number) => x - 5;

const calculate = compose(subtractFive, multiplyByTwo, addTen);
console.log(calculate(5)); // 25
// Execution: addTen(5) → 15, multiplyByTwo(15) → 30, subtractFive(30) → 25

// String transformations
const trim = (s: string) => s.trim();
const uppercase = (s: string) => s.toUpperCase();
const addExclamation = (s: string) => s + "!";

const shout = compose(addExclamation, uppercase, trim);
console.log(shout("  hello  ")); // "HELLO!"

Use Cases ​

  • Data transformation pipelines: Chain multiple transformations
  • Functional programming: Build complex operations from simple ones
  • Middleware patterns: Process data through multiple stages

Notes ​

  • Functions are executed from right to left
  • Each function must accept and return the same type
  • For left-to-right execution, use pipe instead
  • TypeScript ensures type compatibility across the chain

curry ​

Transforms a function with multiple arguments into a sequence of functions, each taking a single argument.

Signature ​

typescript
function curry<F extends (...args: any[]) => any>(fn: F): CurriedFunction<F>;

Parameters ​

  • fn - The function to curry

Returns ​

A curried version of the function that can be called with one argument at a time.

Example ​

typescript
import { curry } from "jalutils/function";

// Basic currying
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

// String formatting
const format = (template: string, name: string, age: number) =>
  template.replace("{name}", name).replace("{age}", String(age));

const curriedFormat = curry(format);
const greet = curriedFormat("Hello {name}, you are {age} years old");

console.log(greet("Alice")(30)); // "Hello Alice, you are 30 years old"
console.log(greet("Bob", 25)); // "Hello Bob, you are 25 years old"

// Partial application
const multiply = (a: number, b: number, c: number) => a * b * c;
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
const quadruple = double(2);

console.log(quadruple(5)); // 20

Use Cases ​

  • Partial application: Create specialized functions from general ones
  • Functional composition: Build complex operations incrementally
  • Configuration: Pre-configure functions with default values
  • Reusability: Create function variants without repetition

Notes ​

  • Allows calling with any number of arguments at each step
  • Once all arguments are provided, the original function is executed
  • TypeScript preserves full type safety throughout the currying chain
  • Works with functions of any arity

debounce ​

Delays function execution until after a specified wait time has elapsed since the last time it was invoked. Perfect for handling rapid events like typing, scrolling, or resizing.

Signature ​

typescript
function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
): (...args: Parameters<T>) => void;

Parameters ​

  • func - The function to debounce
  • wait - The number of milliseconds to delay

Returns ​

A debounced version of the function that delays execution.

Example ​

typescript
import { debounce } from "jalutils/function";

// Search input handler
const handleSearch = debounce((query: string) => {
  console.log("Searching for:", query);
  // API call here
}, 300);

// User types: "hello"
handleSearch("h"); // Cancelled
handleSearch("he"); // Cancelled
handleSearch("hel"); // Cancelled
handleSearch("hell"); // Cancelled
handleSearch("hello"); // Executes after 300ms

// Window resize handler
const handleResize = debounce(() => {
  console.log("Window resized");
  // Expensive recalculation here
}, 500);

window.addEventListener("resize", handleResize);

// Form validation
const validateEmail = debounce((email: string) => {
  if (email.includes("@")) {
    console.log("Valid email");
  }
}, 250);

Use Cases ​

  • Search inputs: Wait until user stops typing before making API calls
  • Form validation: Validate after user finishes input
  • Window resize: Perform expensive calculations only after resizing stops
  • Auto-save: Save draft only after user stops editing

Notes ​

  • Resets the timer on each call
  • Only executes the function after the specified wait time with no new calls
  • The debounced function does not return a value
  • TypeScript preserves the parameter types of the original function

flow ​

Provides flexible function composition with configurable direction (left-to-right or right-to-left).

Signature ​

typescript
function flow<T>(
  direction: "left" | "right",
  ...fns: Array<(arg: T) => T>
): (arg: T) => T;

Parameters ​

  • direction - Execution direction: "left" for left-to-right, "right" for right-to-left
  • fns - Functions to compose

Returns ​

A new function that executes the input functions in the specified direction.

Example ​

typescript
import { flow } from "jalutils/function";

// Left-to-right flow
const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;
const square = (x: number) => x * x;

const leftFlow = flow("left", addOne, double, square);
console.log(leftFlow(2)); // 36
// Execution: addOne(2) → 3, double(3) → 6, square(6) → 36

// Right-to-left flow
const rightFlow = flow("right", addOne, double, square);
console.log(rightFlow(2)); // 5
// Execution: square(2) → 4, double(4) → 8, addOne(8) → 9

// String processing with left flow
const trim = (s: string) => s.trim();
const lowercase = (s: string) => s.toLowerCase();
const removeSpaces = (s: string) => s.replace(/\s+/g, "");

const normalize = flow("left", trim, lowercase, removeSpaces);
console.log(normalize("  Hello World  ")); // "helloworld"

Use Cases ​

  • Flexible pipelines: Choose execution direction based on context
  • Dynamic composition: Switch between left and right composition
  • Adapting to conventions: Match team or library conventions

Notes ​

  • "left" direction internally uses pipe (left-to-right)
  • "right" direction internally uses compose (right-to-left)
  • All functions must accept and return the same type
  • TypeScript ensures type safety across the composition

memoize ​

Caches function results based on arguments, improving performance for expensive computations with repeated inputs.

Signature ​

typescript
function memoize<T extends (...args: any[]) => any>(
  func: T,
): (...args: Parameters<T>) => ReturnType<T>;

Parameters ​

  • func - The function to memoize

Returns ​

A memoized version of the function that caches results.

Example ​

typescript
import { memoize } from "jalutils/function";

// Expensive calculation
const fibonacci = memoize((n: number): number => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(40)); // Slow first time
console.log(fibonacci(40)); // Instant (cached)

// API data fetching
const fetchUser = memoize(async (userId: number) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

await fetchUser(1); // Makes API call
await fetchUser(1); // Returns cached result

// Complex object transformation
const processData = memoize((data: string) => {
  console.log("Processing...");
  return data.split(",").map((item) => item.trim().toUpperCase());
});

processData("apple,banana,cherry"); // Logs "Processing..."
processData("apple,banana,cherry"); // Instant (cached, no log)

// With multiple arguments
const multiply = memoize((a: number, b: number) => {
  console.log(`Computing ${a} * ${b}`);
  return a * b;
});

multiply(5, 3); // Logs "Computing 5 * 3", returns 15
multiply(5, 3); // Returns 15 (cached, no log)
multiply(5, 4); // Logs "Computing 5 * 4", returns 20

Use Cases ​

  • Recursive algorithms: Fibonacci, factorial, dynamic programming
  • API calls: Cache responses for repeated requests
  • Data transformations: Avoid reprocessing the same data
  • Heavy computations: Mathematical operations, parsing, formatting

Cache Key ​

The cache key is created by JSON stringifying the arguments:

  • func(1, 2) → cache key: "[1,2]"
  • func("test") → cache key: "["test"]"

Notes ​

  • Cache persists for the lifetime of the memoized function
  • Works with any number and type of arguments
  • Cache size is unlimited (consider memory for long-running applications)
  • Arguments are compared using JSON serialization
  • Not suitable for functions with non-serializable arguments (functions, symbols, etc.)
  • TypeScript preserves both parameter and return types

once ​

Ensures a function can only be executed once. Subsequent calls return the cached result from the first execution.

Signature ​

typescript
function once<T extends (...args: unknown[]) => unknown>(fn: T): T;

Parameters ​

  • fn - The function to execute only once

Returns ​

A wrapped function that executes only on the first call and returns the cached result thereafter.

Example ​

typescript
import { once } from "jalutils/function";

// Expensive initialization
const initialize = once(() => {
  console.log("Initializing...");
  return { status: "ready" };
});

console.log(initialize()); // Logs "Initializing...", returns { status: "ready" }
console.log(initialize()); // Returns { status: "ready" } (no log)
console.log(initialize()); // Returns { status: "ready" } (no log)

// Database connection
const connectDB = once(() => {
  console.log("Connecting to database...");
  return { connection: "active" };
});

const db1 = connectDB(); // Logs "Connecting to database..."
const db2 = connectDB(); // Returns same connection, no log

// Event listener setup
const setupListeners = once(() => {
  console.log("Setting up listeners");
  window.addEventListener("resize", () => console.log("resized"));
});

setupListeners(); // Sets up listeners
setupListeners(); // Does nothing

Use Cases ​

  • Initialization: Ensure setup code runs exactly once
  • Singleton pattern: Create single instances
  • Event listeners: Prevent duplicate listener registration
  • Expensive operations: Cache results of one-time computations
  • Configuration loading: Load config only once

Notes ​

  • The result of the first call is cached permanently
  • Subsequent calls ignore any arguments passed
  • Works with synchronous and asynchronous functions
  • TypeScript preserves the function signature
  • No way to reset or re-execute the function

partial ​

Creates a new function with some arguments pre-filled, allowing partial application of function arguments.

Signature ​

typescript
function partial<T extends unknown[], U>(
  fn: (...args: T) => U,
  ...presetArgs: Partial<T>
): (...laterArgs: Partial<T>) => U;

Parameters ​

  • fn - The function to partially apply
  • presetArgs - Arguments to pre-fill

Returns ​

A new function that accepts the remaining arguments.

Example ​

typescript
import { partial } from "jalutils/function";

// Basic partial application
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`;
const sayHello = partial(greet, "Hello");

console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHello("Bob")); // "Hello, Bob!"

// Multiple arguments
const multiply = (a: number, b: number, c: number) => a * b * c;
const multiplyBy2 = partial(multiply, 2);
const multiplyBy2And3 = partial(multiply, 2, 3);

console.log(multiplyBy2(3, 4)); // 24
console.log(multiplyBy2And3(5)); // 30

// API client configuration
const fetchData = (baseUrl: string, endpoint: string, params: string) =>
  `${baseUrl}/${endpoint}?${params}`;

const apiClient = partial(fetchData, "https://api.example.com");
const usersAPI = partial(fetchData, "https://api.example.com", "users");

console.log(apiClient("posts", "page=1")); // "https://api.example.com/posts?page=1"
console.log(usersAPI("id=123")); // "https://api.example.com/users?id=123"

Use Cases ​

  • Configuration: Pre-configure functions with common arguments
  • API clients: Create specialized clients from generic functions
  • Event handlers: Bind context or data to handlers
  • Reusability: Create function variants without duplication

Notes ​

  • Pre-filled arguments are applied in order
  • Throws an error if not enough arguments are provided
  • TypeScript maintains type safety for preset and later arguments
  • Arguments must be defined (not undefined) to be considered preset

pipe ​

Composes multiple functions from left to right. The output of each function is passed as input to the next function.

Signature ​

typescript
function pipe<T>(...fns: Array<(arg: T) => T>): (arg: T) => T;

Parameters ​

  • fns - Functions to pipe (executed left to right)

Returns ​

A new function that represents the piped composition of the input functions.

Example ​

typescript
import { pipe } from "jalutils/function";

// Simple transformations
const addTen = (x: number) => x + 10;
const multiplyByTwo = (x: number) => x * 2;
const subtractFive = (x: number) => x - 5;

const calculate = pipe(addTen, multiplyByTwo, subtractFive);
console.log(calculate(5)); // 25
// Execution: addTen(5) → 15, multiplyByTwo(15) → 30, subtractFive(30) → 25

// String processing
const trim = (s: string) => s.trim();
const lowercase = (s: string) => s.toLowerCase();
const replaceSpaces = (s: string) => s.replace(/\s+/g, "-");

const slugify = pipe(trim, lowercase, replaceSpaces);
console.log(slugify("  Hello World  ")); // "hello-world"

// Data transformation
const parseJSON = (s: string) => JSON.parse(s);
const extractName = (obj: any) => obj.name;
const uppercase = (s: string) => s.toUpperCase();

const processData = pipe(parseJSON, extractName, uppercase);
console.log(processData('{"name":"alice"}')); // "ALICE"

Use Cases ​

  • Data pipelines: Chain transformations in reading order
  • Functional programming: Build complex operations from simple ones
  • Stream processing: Process data through sequential stages

Notes ​

  • Functions are executed from left to right
  • Each function must accept and return the same type
  • More intuitive than compose for most developers
  • TypeScript ensures type compatibility across the chain

throttle ​

Limits function execution to once per specified time interval. Unlike debounce, throttle ensures the function is called at regular intervals during continuous events.

Signature ​

typescript
function throttle<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
): (...args: Parameters<T>) => void;

Parameters ​

  • func - The function to throttle
  • wait - The number of milliseconds between allowed executions

Returns ​

A throttled version of the function.

Example ​

typescript
import { throttle } from "jalutils/function";

// Scroll event handler
const handleScroll = throttle(() => {
  console.log("Scroll position:", window.scrollY);
  // Update scroll indicator
}, 100);

window.addEventListener("scroll", handleScroll);

// Mouse move tracker
const trackMouse = throttle((e: MouseEvent) => {
  console.log(`Position: ${e.clientX}, ${e.clientY}`);
}, 50);

document.addEventListener("mousemove", trackMouse);

// Button click protection
const submitForm = throttle(() => {
  console.log("Form submitted");
  // API call
}, 1000);

// API polling
const checkStatus = throttle(() => {
  fetch("/api/status")
    .then((res) => res.json())
    .then((data) => console.log(data));
}, 5000);

Use Cases ​

  • Scroll events: Update UI during scrolling without overwhelming performance
  • Mouse tracking: Track mouse position at regular intervals
  • Button clicks: Prevent multiple rapid submissions
  • API polling: Regular status checks without overwhelming the server
  • Game loops: Limit frame rate or update frequency

Debounce vs Throttle ​

AspectDebounceThrottle
ExecutionAfter inactivity periodAt regular intervals
FrequencyOnce after events stopMultiple times during events
Use caseSearch input, form validationScroll, mouse move, animations

Notes ​

  • Executes immediately on first call, then enforces the wait period
  • Subsequent calls within the wait period are ignored
  • Does not queue or batch function calls
  • TypeScript preserves the parameter types of the original function

Import ​

typescript
import {
  compose,
  curry,
  debounce,
  flow,
  memoize,
  once,
  partial,
  pipe,
  throttle,
} from "jalutils/function";
typescript
import {
  compose,
  curry,
  debounce,
  flow,
  memoize,
  once,
  partial,
  pipe,
  throttle,
} from "jalutils";

Released under the MIT License.