npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

yon-utils

v0.2.2

Published

Some utils that I repeated too many times. DRY!

Readme

yon-utils

Some utils and remix that I repeated in many projects.

This package includes some light-weight alternatives to packages like:

our | is alternative to / remix of ------- | ----------------- elt / clsx | clsx, classnames, h, hyperscript maybeAsync / makePromise / PromiseEx | imperative-promise, bluebird stringHash | cyrb53, murmurhash ... randomStr | nanoid <some lodash-like functions> | lodash

There are also some interesting original utils like shallowEqual / newFunction / toArray / getVariableName etc. Feel free to explore!

Other cool third-party libraries:

QuickStart

Play in CodeSandbox

All modules are shipped as ES modules and tree-shakable.

  • via package manager

    npm install yon-utils

  • via import within <script type="module">

    import { elt } from "https://unpkg.com/yon-utils"

ToC

| category | exports | | --- | ------- | | convertor | methods: jsonSchemaToTypeScript | | dom | methods: clsx / elt | | execute | methods: newFunction / noop / retry / pollUntil | | flow | methods: fnQueue / makeAsyncIterator / makeEffect / withDefer / withAsyncDefer / createWorkerHandler / createWorkerDispatcher | | interaction | methods: writeClipboard / readClipboard / modKey / startMouseMove vars: IS_MAC / MOD_KEY / MOD_KEY_LABEL types: KeyboardEventLike / MouseMoveInfo / MouseMoveInitOptions | | iterable | methods: toArray / find / findMap / mapFilter / reduce / head / contains / forEach / filterMap types: OneOrMany / Predicate / PredicateAndMap / CollectionOf / IterItem | | manager | methods: getObjectUniqueKey classes: ModuleLoader / CircularDependencyError types: ModuleLoaderCache / ModuleLoaderSource | | math | methods: isInsideRect / isRectEqual / getRectIntersection / getRectUnion / approx / clamp / lerp / easeOut / randomNum types: RectLike / RectLikeXYWH / RectLikeLTWH | | promise | methods: delay / debouncePromise / maybeAsync / makePromise classes: PromiseEx / PromisePendingError / SwappablePromise types: ImperativePromiseEx / SwappablePromiseExecutor | | string | methods: stringHash / getVariableName / bracket / randomStr / nanoid | | value | methods: shallowEqual / tryParseJSON / isNil / isObject / isThenable types: MaybePromise / Falsy / Nil / Fn |

🧩 convertor/json-schema

jsonSchemaToTypeScript(schema, indent?)

  • schema: JSONSchema

  • indent?: number

  • returns: string

a simple util to translate JSON Schema to TypeScript type declaration, with shortest comment (single line if possible)

🧩 dom/clsx

clsx(...args)

  • ...args: any[]

  • returns: string

construct className strings conditionally.

can be an alternative to classnames(). modified from lukeed/clsx. to integrate with Tailwind VSCode, read this

🧩 dom/elt

elt(tagName, attrs, ...children)

  • tagName: string — e.g. "button" or "button.my-btn#myId"

  • attrs: anyclass (processed by clsx), style (string/object), onClick (auto addEventListener)

  • ...children: any[] — strings, numbers, nodes

  • returns: HTMLElement

Simplified document.createElement with support for class/id shortcuts, event listeners, and JSX.

Works with JSX: /** @jsx elt */

Remarks

  • Event modifier: onClick_capture / onClick_once / onClick_self
    • className is alias of class, supports { 'my-button': true, 'is-primary': XXX }

Example

elt('button.myButton', { onclick: () => alert('hi') }, 'Click Me!') Click Me

🧩 execute/function

newFunction(argumentNames, functionBody, options?)

  • argumentNames: NameArray<ARGS> — a string[] of argument names

  • functionBody: string — the function body

  • options?: { async? }

    • async?: boolean | undefined — set to true if the code contains await, the new function will be an async function
  • returns: Fn<RESULT, ARGS>

like new Function but with more reasonable options and api

noop()

  • returns: void

The nothing-to-do function

🧩 execute/retry

retry(fn, opts?)

  • fn: () => T | Nil | Promise<T | Nil> — Function to retry

  • opts?: { duration?, interval?, signal? }

    • duration?: number | undefined

    • interval?: number | undefined

    • signal?: AbortSignal | undefined

  • returns: Promise<T> — Promise resolving to the first successful result

Retries fn() until it returns a non-null value without throwing.

Throws when timeout or aborted.

pollUntil(fn, opts?)

  • fn: () => T | Nil | Promise<T | Nil> — - Function to retry

  • opts?: { duration?, interval?, signal? }

    • duration?: number | undefined

    • interval?: number | undefined

    • signal?: AbortSignal | undefined

  • returns: Promise<T>

Alias for retry. Polls a function until it returns a non-null value, or throws if timeout or aborted.

🧩 flow/fnQueue

fnQueue()

  • returns: { tap, tapSilent, call, queue }

    • tap: AddCallbacks<Args> — add one or more functions.

    • tapSilent: AddCallbacks<Args> — add functions, and will silently ignore their errors

    • call: (...args: Args) => void — run functions. if fnQueue is async, returns Promise

    • queue: { silent?: boolean | undefined; fn: Fn<any, Args>; }[] — the queued functions

Store a list of functions, and execute them in order.

  • Use case: 🧹 disposer (clean resources) / ⚡ event emitter / 🪢 tapable-like middleware
  • Defaults: sync, FIFO, errors will abort

Use decorators or options, to customize a fnQueue:

  • fnQueue.async() to create async queue -- the call() will return a Promise instead.
  • fnQueue.filo() to create FILO queue.
  • fnQueue.onetime() to clear the queue after each call.
  • fnQueue({ error: 'ignore' }) to ignore errors.

Options can be combined, like fnQueue.async.onetime() -- see example below.

Example

// create an async fnQueue with options ...
const disposer = fnQueue.async.onetime({ error: 'ignore' });

try {
  const srcFile = await openFile(path1);
  disposer.tap(() => srcFile.close());

  const dstFile = await openFile(path2);
  disposer.tap(() => dstFile.close());

  await copyData(srcFile, dstFile);
} finally {
  await disposer.call();
}
  • async: Factory<Promise<void>>

    • async: Factory<Promise<void>>

    • filo: Factory<Promise<void>> — change execution order to FILO (first-in, last-out, like a stack)

    • onetime: Factory<Promise<void>> — after each call, clear the queue

  • filo: Factory<void> — change execution order to FILO (first-in, last-out, like a stack)

    • async: Factory<Promise<void>>

    • filo: Factory<void> — change execution order to FILO (first-in, last-out, like a stack)

    • onetime: Factory<void> — after each call, clear the queue

  • onetime: Factory<void> — after each call, clear the queue

    • async: Factory<Promise<void>>

    • filo: Factory<void> — change execution order to FILO (first-in, last-out, like a stack)

    • onetime: Factory<void> — after each call, clear the queue

🧩 flow/makeAsyncIterator

makeAsyncIterator()

  • returns: { write(value: T): void; end(error?: any): void; } & AsyncIterableIterator<T>

    • write: (value: T) => void

    • end: (error?: any) => void

Help you convert a callback-style stream into an async iterator. Also works on "observable" value like RxJS.

You can think of this as a simplified new Readable({ ... }) without headache.

Example

const iterator = makeAsyncIterator();

socket.on('data', value => iterator.write(value));
socket.on('end', () => iterator.end());
socket.on('error', (err) => iterator.end(err));

for await (const line of iterator) {
  console.log(line);
}

🧩 flow/makeEffect

makeEffect(fn, isEqual?)

  • fn: (input: T, previous: T | undefined) => void | (() => void)

  • isEqual?: (x: T, y: T) => boolean

  • returns: { cleanup, value }

    • cleanup: () => void — invoke last cleanup function, and reset value to undefined

    • value?: T | undefined — get last received value, or undefined if effect was clean up

Wrap fn and create an unary function. The actual fn() executes only when the argument changes.

Meanwhile, your fn may return a cleanup function, which will be invoked before new fn() calls -- just like React's useEffect

The new unary function also provide cleanup() method to forcedly do the cleanup, which will also clean the memory of last input.

Example

const sayHi = makeEffect((name) => {
  console.log(`Hello, ${name}`);
  return () => {
    console.log(`Goodbye, ${name}`);
  }
});

sayHi('Alice');  // output: Hello, Alice
sayHi('Alice');  // no output
sayHi('Bob');    // output: Goodbye, Alice   Hello, Bob
sayHi.cleanup(); // output: Goodbye, Bob
sayHi.cleanup(); // no output

🧩 flow/withDefer

withDefer(fn)

  • fn: (defer: AddCallbacks<[]>) => Ret

  • returns: Ret

Get rid of try catch finally hells! Use defer(callback) to clean up resources, and they will run in finally stage.

Works on both sync and async procedures.

For sync functions:

// sync
const result = withDefer((defer) => {
  const file = openFileSync('xxx')
  defer(() => closeFileSync(file))  // <- register callback

  const parser = createParser()
  defer(() => parser.dispose())  // <- register callback

  return parser.parse(file.readSync())
})

For async functions, use withAsyncDefer

// async
const result = await withAsyncDefer(async (defer) => {
  const file = await openFile('xxx')
  defer(async () => await closeFile(file))  // <- defer function can be async now!

  const parser = createParser()
  defer(() => parser.dispose())  // <-

  return parser.parse(await file.read())
})

Error handling

If one callback throws, rest callbacks still work. And you get the last error thrown.

To suppress a callback's throwing, use defer.silent(callback)

defer.silent(() => closeFile(file))  // will never throws

Remarks

Refer to TypeScript using syntax, TC39 Explicit Resource Management and GoLang's defer keyword.

withAsyncDefer(fn)

  • fn: (defer: AddCallbacks<[]>) => Ret

  • returns: Ret

Same as withDefer, but this returns a Promise, and supports async callbacks.

🧩 flow/worker-rpc

createWorkerHandler(methods)

  • methods: T — Object containing method implementations that can be called remotely

  • returns: (payload: RpcPayload) => void — A handler function that processes incoming RPC payloads

Creates a handler function for processing RPC requests in a Worker.

Example

// In your worker file:
import { createWorkerHandler } from './worker-rpc';

const handler = createWorkerHandler({
  async getData(id) {
    // implementation
    return result;
  }
});

self.onmessage = (e) => { 
  if (e.data?.type === 'myCall') handler(e.data.payload);
}

createWorkerDispatcher(postMessage)

  • postMessage: (payload: RpcPayload, transferable: Transferable[]) => void — Function that sends messages to the worker (typically worker.postMessage)

  • returns: T — A proxy object where each property access creates a function that calls the corresponding method in the worker

Creates a proxy object that dispatches method calls to a Worker via RPC.

Example

// In your main thread:
import { createWorkerDispatcher } from './worker-rpc';

const worker = new Worker('path/to/worker.js');
const api = createWorkerDispatcher<MyWorkerAPI>((payload, transferable) => 
  worker.postMessage({
    type: 'myCall',
    payload,
  }, transferable)
);

// Now you can call worker methods as if they were local:
// No need to worry about initiating, it automatically wait for the worker to be ready
const result = await api.getData(123);

🧩 interaction/clipboard

writeClipboard(text)

  • text: string

  • returns: Promise<void>

write text to clipboard, with support for insecure context and legacy browser!

note: if you are in HTTPS and modern browser, you can directly use navigator.clipboard.writeText() instead.

readClipboard(timeout?)

  • timeout?: number — default 1500

  • returns: Promise<string>

read clipboard text.

if user rejects or hesitates about the permission for too long, this will throw an Error.

🧩 interaction/keyboard

modKey(ev)

  • ev: KeyboardEventLike

  • returns: number

get Modifier Key status from a Event

Remarks

  1. use modKey.Mod to indicate if the key is (Cmd) on Mac, or Ctrl on Windows/Linux
  2. use | (or operator) to combine modifier keys. see example below.

Example

if (modKey(ev) === (modKey.Mod | modKey.Shift) && ev.code === 'KeyW') {
  // Ctrl/Cmd + Shift + W, depends on the OS
}
  • None: 0

  • Ctrl: 1

  • Cmd: 2

  • Shift: 4

  • Alt: 8

  • Mod: 1 | 2 — equals to Ctrl or Cmd, depending on the OS

IS_MAC

boolean

MOD_KEY

the proper way to get modKey status from KeyboardEvent

"metaKey" | "ctrlKey"

MOD_KEY_LABEL

the proper label of modKey. eg: for Mac, Ctrl for Windows/Linux

"⌘" | "Ctrl"

interface KeyboardEventLike

  • ctrlKey?: boolean | undefined

  • metaKey?: boolean | undefined

  • shiftKey?: boolean | undefined

  • altKey?: boolean | undefined

🧩 interaction/mouseMove

startMouseMove({ initialEvent, onMove, onEnd })

  • __0: MouseMoveInitOptions

  • returns: Promise<MouseMoveInfo> — a Promise with final position when user releases button

Powerful tool to implement any drag / resize action easily!

While dragging, it tracks the cursor's movement and reports to onMove(...). And when user releases the button, it will call onEnd(...).

  1. the element needs touch-action: none style to prevent scrolling on mobile

  2. use within pointerdown event

  3. event.preventDefault() is necessary too

Example

button.style.touchAction = 'none' // CSS touch-action: none
button.addEventListener('pointerdown', event => {
  event.preventDefault();
  startMouseMove({
    initialEvent: event,
    onMove({ deltaX, deltaY }) { ... },
    onEnd({ deltaX, deltaY }) { ... },
  });
});

interface MouseMoveInfo

  • clientX: number

  • clientY: number

  • deltaX: number

  • deltaY: number

  • duration: number — in milliseconds, since startMouseMove called

  • event: MouseEvent | PointerEvent

  • pointerId: number | false

  • cancelled?: boolean | undefined

interface MouseMoveInitOptions

  • initialEvent: MouseEvent | PointerEvent

  • onMove?: ((data: MouseMoveInfo) => void) | undefined

  • onEnd?: ((data: MouseMoveInfo) => void) | undefined

🧩 iterable/iterable

toArray(value)

  • value?: OneOrMany<T>

  • returns: NonNullable<T>[]

Input anything, always return an array.

  • If the input is a single value that is not an array, wrap it as a new array.
  • If the input is already an array, it returns a shallow copy.
  • If the input is an iterator, it is equivalent to using Array.from() to process it.

Finally before returning, all null and undefined will be omitted

find(iterator, predicate)

  • iterator?: Nil | Iterable<T>

  • predicate: Predicate<T>

  • returns: T | undefined

Like Array#find, but the input could be a Iterator (for example, from generator, Set or Map)

findMap(iterator, map)

  • iterator?: Nil | Iterable<Nil | T>

  • map: PredicateAndMap<T, U>

  • returns: U | undefined

Find the first item that matches the predicate, and then map it to a new value.

map function must return non-null value, otherwise it will be skipped.

mapFilter(iterator, map)

  • iterator?: Nil | Iterable<Nil | T>

  • map: PredicateAndMap<T, U>

  • returns: U[]

Type-safe Shortcut for .map(fn).filter(val => !isNil(val))

map function must return non-null value, otherwise it will be skipped.

reduce(iterator, initial, reducer)

  • iterator?: Nil | Iterable<T>

  • initial: U

  • reducer: (agg: U, item: T, index: number) => U

  • returns: U

Like Array#reduce, but the input could be a Iterator (for example, from generator, Set or Map)

head(iterator)

  • iterator?: Nil | Iterable<T>

  • returns: T | undefined

Take the first result from a Iterator

contains(collection, item)

  • collection?: Nil | CollectionOf<T>

  • item: T

  • returns: boolean

input an array / Set / Map / WeakSet / WeakMap / object etc, check if it contains the item

forEach(objOrArray, iter)

  • objOrArray: U

  • iter: (value: U[keyof U], key: keyof U, whole: U) => void

  • returns: void

a simple forEach iterator that support both Array | Set | Map | Object | Iterable as the input

when met plain object, it works like forIn of lodash.

type OneOrMany<T>

export type OneOrMany<T> = Iterable<T | Nil> | T | Nil

type Predicate<T>

export type Predicate<T> = (value: T, index: number) => boolean;

type PredicateAndMap<T, U>

export type PredicateAndMap<T, U> = (value: T, index: number) => U | Nil;

filterMap(iterator, map)

  • iterator?: Nil | Iterable<Nil | T>

  • map: PredicateAndMap<T, U>

  • returns: U[]

alias of mapFilter

type CollectionOf<T>

export type CollectionOf<T> =
  | ReadonlyArray<T>
  | Set<T> | Map<T, any>
  | (T extends object ? WeakMap<T, any> | WeakSet<T> : never)
  | (T extends string ? Record<T, any> : never)

type IterItem<T>

export type IterItem<T> = T extends Iterable<infer U> ? U : never;

🧩 manager/moduleLoader

new ModuleLoader<T>(source)

  • source: ModuleLoaderSource<T>

All-in-one ModuleLoader, support both sync and async mode, can handle circular dependency problem.

Example in Sync

const loader = new ModuleLoader({
  // sync example
  resolve(query, { load }) {
    if (query === 'father') return 'John'
    if (query === 'mother') return 'Mary'

    // simple alias: just `return load('xxx')`
    if (query === 'mom') return load('mother')

    // load dependency
    // - `load('xxx').value` for sync, don't forget .value
    // - `await load('xxx')` for async
    if (query === 'family') return `${load('father').value} and ${load('mother').value}`

    // always return something as fallback
    return 'bad query'
  }
})

console.log(loader.load('family').value)  // don't forget .value

Example in Async

const loader = new ModuleLoader({
  // async example
  async resolve(query, { load }) {
    if (query === 'father') return 'John'
    if (query === 'mother') return 'Mary'

    // simple alias: just `return load('xxx')`
    if (query === 'mom') return load('mother')

    // load dependency
    // - `await load('xxx')` for async
    // - no need `.value` in async mode
    if (query === 'family') return `${await load('father')} and ${await load('mother')}`

    // always return something as fallback
    return 'bad query'
  }
})

console.log(await loader.load('family'))  // no need `.value` with `await`

ModuleLoader # cache

  • type: ModuleLoaderCache<{ dependencies?: string[] | undefined; promise: PromiseEx<T>; }>

ModuleLoader # load(query)

  • query: string

  • returns: PromiseEx<T>

fetch a module

ModuleLoader # getDependencies(query, deep?)

  • query: string

  • deep?: boolean

  • returns: PromiseEx<string[]>

get all direct dependencies of a module.

note: to get reliable result, this will completely load the module and deep dependencies.

new CircularDependencyError(query, queryStack)

  • query: string

  • queryStack: string[]

The circular dependency Error that ModuleLoader might throw.

CircularDependencyError # query

  • type: string

the module that trying to be loaded.

CircularDependencyError # queryStack

  • type: string[]

the stack to traceback the loading progress.

CircularDependencyError # name

  • type: string

always 'CircularDependencyError'

type ModuleLoaderCache<T>

used by ModuleLoader

  • get: (query: string) => T | undefined

  • set: (query: string, value: T) => any

  • delete: (query: string) => any

  • clear: () => void

interface ModuleLoaderSource<T>

used by ModuleLoader

  • resolve: (query: string, ctx: { load(target: string): PromiseEx<T>; noCache<T>(value: T): T; }) => MaybePromise<T> — You must implement a loader function. It parse query and returns the module content.

    1. It could be synchronous or asynchronous, depends on your scenario.
    2. You can use load() from ctx to load dependencies. Example: await load("common") or load("common").value
    3. All queries are cached by default. To bypass it, use ctx.noCache. Example: return noCache("404: not found")
  • cache?: ModuleLoaderCache<any> | undefined

🧩 manager/objectKey

getObjectUniqueKey(obj)

  • obj: any — The object or function to get a unique key for

  • returns: number | undefined — A unique numeric identifier, or undefined if input invalid

Gets a unique numeric key for an object. (value >= 1)

Uses a global WeakMap store to ensure the same object always returns the same key across calls.

Remarks

Create a new isolated store with getObjectUniqueKey.createStore(onCreate?)

  • returns (obj) => key function
  • onCreate(obj, key) - optional, called when a new key is created.

Example

const obj = {};
getObjectUniqueKey(obj); // 1
getObjectUniqueKey(obj); // 1 (same key)
getObjectUniqueKey({}); // 2 (different object)
  • createStore: <T = any>(onCreate?: ((obj: T, key: number) => void) | undefined) => ObjectKeyGetter

🧩 math/geometry

isInsideRect(x, y, rect)

  • x: number — The x-coordinate of the point.

  • y: number — The y-coordinate of the point.

  • rect: RectLike — The rectangle to check against.

  • returns: boolean

Determines whether a point (x, y) is inside a rectangle.

isRectEqual(rect1, rect2, epsilon?)

  • rect1?: Nil | RectLike — The first rectangle to compare.

  • rect2?: Nil | RectLike — The second rectangle to compare.

  • epsilon?: number | undefined — The maximum difference allowed between the values of the rectangles' properties.

  • returns: boolean

Determines whether two rectangles are equal.

getRectIntersection(...rects)

  • ...rects: (Nil | RectLike)[] — The rectangles

  • returns: RectLike — The intersection rectangle. Can be passed to DOMRect.fromRect(.)

Calculates the intersection of rectangles.

getRectUnion(...rects)

  • ...rects: (Nil | RectLike)[] — The rectangles

  • returns: RectLike — The union rectangle. Can be passed to DOMRect.fromRect(.)

Calculates the union (out bounding box) of rectangles.

type RectLike

a interface that fits DOMRect and many other situation

export type RectLike = RectLikeXYWH | RectLikeLTWH;

interface RectLikeXYWH

  • x: number

  • y: number

  • width: number

  • height: number

interface RectLikeLTWH

  • left: number

  • top: number

  • width: number

  • height: number

🧩 math/number

approx(a, b, epsilon?)

  • a: number

  • b: number

  • epsilon?: number — The maximum difference allowed between the two numbers. Defaults to 0.001.

  • returns: boolean

Determines if two numbers are approximately equal within a given epsilon.

clamp(n, min, max)

  • n: number

  • min: number

  • max: number

  • returns: number

Clamp a number between min and max

lerp(min, max, t)

  • min: number

  • max: number

  • t: number — The interpolation value clamped between 0 and 1

  • returns: number

Linearly interpolate

Example

const value = lerp(0, 2, 0.5) // value will be 1

easeOut(t)

  • t: number

  • returns: number

simple ease-out function, useful for animation, tween, and fake progress bar

Example

// a fake progress bar never reach 100%
const sinceTime = Date.now()
const progress = 0.95 * easeOut((Date.now() - sinceTime) / 5000)

randomNum(min, max)

  • min: number

  • max: number

  • returns: number

Get a random number from [min, max)

🧩 promise/misc

delay(milliseconds)

  • milliseconds: number

  • returns: Promise<void>

debouncePromise(fn)

  • fn: () => Promise<T> — The function to be debounced.

  • returns: { (): Promise<T>; clear(): void; } — The debounced function.

    • clear: () => void — make next call always return new Promise

Creates a debounced version of a function that returns a promise.

The returned function will ensure that only one Promise is created and executed at a time, even if the debounced function is called multiple times before last Promise gets finished.

All suppressed calls will get the last started Promise.

Remarks

use lodash's throttle() if you want to support things like trailing and cache-timeout (aka. interval) option.

🧩 promise/promise

maybeAsync(input)

  • input: T | Promise<T> | (() => T | Promise<T>) — your sync/async function to run, or just a value

  • returns: PromiseEx<Awaited<T>> — a crafted Promise that exposes { status, value, reason }, whose status could be "pending" | "fulfilled" | "rejected"

Run the function, return a crafted Promise that exposes status, value and reason

If input is sync function, its result will be stored in promise.value and promise.status will immediately be set as "fulfilled"

Useful when you are not sure whether fn is async or not.

makePromise()

  • returns: ImperativePromiseEx<T>

Create an imperative Promise.

Returns a Promise with these 2 methods exposed, so you can control its behavior:

  • .resolve(result)
  • .reject(error)

Besides, the returned Promise will expose these useful properties so you can get its status easily:

  • .wait([timeout]) — wait for result, if timeout set and exceeded, a PromisePendingError will be thrown
  • .status — could be "pending" | "fulfilled" | "rejected"
  • .result and .reason
  • .value — fail-safe get result (or cause an Error from rejection, or cause a PromisePendingError if still pending)

Example

const handler = makePromise();

doSomeRequest(..., result => handler.resolve(result));

// wait with timeout
const result = await handler.wait(1000);

// or just await
const result = await handler;

new PromiseEx<T>(executor)

  • executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void

a crafted Promise that exposes { status, value, reason }

Note: please use maybeAsync() or PromiseEx.resolve() to create a PromiseEx

PromiseEx # status

  • type: "pending" | "fulfilled" | "rejected"

PromiseEx # reason

  • type: any

if rejected, get the reason.

PromiseEx # result

  • type: T | undefined

get result, or nothing if not fulfilled.

note: you might need .value which follows fail-fast mentality

PromiseEx # loading

  • type: boolean

equivalent to .status === "pending"

PromiseEx # isPending()

  • returns: boolean

PromiseEx # isFulfilled()

  • returns: boolean

PromiseEx # isRejected()

  • returns: boolean

PromiseEx # value

  • type: T | undefined

fail-fast mentality, safely get the result.

  • if pending, throw new PromisePendingError(this)
  • if rejected, throw .reason
  • if fulfilled, get .result

PromiseEx # wait(timeout?)

  • timeout?: number | undefined

  • returns: Promise<T>

wait for resolved / rejected.

optionally can set a timeout in milliseconds. if timeout, a PromisePendingError will be thrown

PromiseEx # thenImmediately(onfulfilled?, onrejected?)

  • onfulfilled?: Nil | ((value: T) => TResult1 | PromiseLike<TResult1>)

  • onrejected?: Nil | ((reason: any) => TResult2 | PromiseLike<TResult2>)

  • returns: PromiseEx<TResult1 | TResult2>

Like then() but immediately invoke callbacks, if this PromiseEx is already resolved / rejected.

  • resolve: { (): PromiseEx<void>; <T>(input: T): PromiseEx<Awaited<T>>; } — Creates a new resolved promise.

    Creates a new resolved promise for the provided value.

  • reject: <T = never>(reason?: any) => PromiseEx<T> — Creates a new rejected promise for the provided reason.

new PromisePendingError(cause)

  • cause: Promise<any>

Could be thrown from .value and .wait(timeout) of PromiseEx

PromisePendingError # cause

  • type: Promise<any>

type ImperativePromiseEx<T>

export type ImperativePromiseEx<T> = PromiseEx<Awaited<T>> & {
  resolve(result: T | PromiseLike<T>): void
  reject(reason?: any): void
}

🧩 promise/swappablePromise

interface SwappablePromiseExecutor

  • signal: AbortSignal — triggered when current async process is replaced by another run()

  • isCurrent: () => boolean — whether current process is the newest pending Promise. will become false when resolved / rejected / "replaced by other Promise"

  • throwIfNotCurrent: () => void — throw if current process is not the newest pending Promise

new SwappablePromise<T>()

A swappable promise that allows dynamically changing the underlying promise being waited upon.

Useful in avoiding race conditions for UI updates.

This "SwappablePromise" will wait for the newest promise to resolve. You can use run(fn) or swap(promise) to change the underlying promise.

example use cases:

  • only display latest request's result, for multiple requests
  • allow multiple submits, and old requests have "rollback" mechanism (use run() to get context and the signal)

SwappablePromise # wait(timeout?)

  • timeout?: number | undefined

  • returns: Promise<T>

wait for the underlying Promise resolved. the waiting target may be swapped, before resolved or rejected.

if no underlying Promise yet, will keep waiting.

SwappablePromise # run(executor)

  • executor: (ctx: SwappablePromiseExecutor) => T | Promise<T>

  • returns: void

swap to a new async process, and pivot wait() & then() to wait for it

SwappablePromise # swap(promise)

  • promise: T | Promise<T>

  • returns: void

swap to a new Promise. just alias of run(() => promise)

SwappablePromise # reset()

  • returns: void

reset to "pending" status

if someone is waiting, it will keep waiting, NOT aborted.

SwappablePromise # hasTarget()

  • returns: boolean

whether a underlying Promise is set and pending

SwappablePromise # isPending()

  • returns: boolean

whether is pending

SwappablePromise # isFulfilled()

  • returns: boolean

whether is fulfilled

SwappablePromise # isRejected()

  • returns: boolean

whether is rejected

SwappablePromise # then(onfulfilled?, onrejected?)

  • onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined

  • onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined

  • returns: Promise<TResult1 | TResult2>

wait for the underlying promise to resolve. the waiting target may be swapped, before resolved or rejected.

if no underlying Promise yet, will keep waiting.

🧩 string/string

stringHash(str)

  • str: string

  • returns: number

Quickly compute string hash with cyrb53 algorithm

getVariableName(basicName, existingVariables?)

  • basicName: string

  • existingVariables?: CollectionOf<string> | undefined

  • returns: string

input anything weird, get a valid variable name

optionally, you can give a existingVariables to avoid conflicting -- the new name might have a numeric suffix

Example

getVariableName('foo-bar')   // -> "fooBar"
getVariableName('123abc')    // -> "_123abc"
getVariableName('')          // -> "foobar"
getVariableName('name', ['name', 'age'])    // -> "name2"

bracket(text1, text2, brackets?)

  • text1?: string | number | null | undefined

  • text2?: string | number | null | undefined

  • brackets?: string | [string, string] | undefined — defaults to [" (", ")"]

  • returns: string

Add bracket (parenthesis) to text

  • bracket("c_name", "Column Name") => "c_name (Column Name)"
  • bracket("Column Name", "c_name") => "Column Name (c_name)"

If one parameter is empty, it returns the other one:

  • bracket("c_name", null) => "c_name"
  • bracket(null, "c_name") => "c_name"

randomStr(size?, dict?)

  • size?: number — defaults to 16

  • dict?: string — defaults to -\w same as nanoid

  • returns: string

Generate a random string

nanoid(size?, dict?)

  • size?: number — - defaults to 16

  • dict?: string — - defaults to -\w same as nanoid

  • returns: string

alias of randomStr

🧩 value/compare

shallowEqual(objA, objB, depth?)

  • objA: any

  • objB: any

  • depth?: number — defaults to 1

  • returns: boolean

🧩 value/json

tryParseJSON(text, fallback)

  • text: string

  • fallback: T

  • returns: T

safe parse JSON string, return fallback if failed

🧩 value/types

isNil(obj)

  • obj: any

  • returns: boolean

Tell if obj is null or undefined

isObject(obj)

  • obj: any

  • returns: false | "array" | "object"

Tell if obj is Array, Object or other(false)

isThenable(sth)

  • sth: any

  • returns: boolean

type MaybePromise<T>

export type MaybePromise<T> = Promise<T> | T

type Falsy

TypeScript type presents all falsy value, including Nil, false, 0, ""

export type Falsy = null | undefined | false | 0 | "";

type Nil

TypeScript type presents null or undefined

export type Nil = null | undefined;

type Fn<RET, ARGS>

TypeScript type presents any function

export type Fn<RET = any, ARGS extends any[] = any[]> = (...args: ARGS) => RET;