- NAME
- FEATURES
- INSTALLATION
- SYNOPSIS
- DESCRIPTION
- TYPES
- EXPORTS
- DEVELOPMENT
- COMPATIBILITY
- SEE ALSO
- VERSION
- AUTHOR
- COPYRIGHT AND LICENSE
when - subscribe to an event before or after it's fired
- no dependencies
- < 500 B minified + gzipped
- fully typed (TypeScript)
- CDN builds (UMD) - jsDelivr, unpkg
$ npm install @chocolatey/when
import when from '@chocolatey/when'
const onPageShow = when(done => {
window.addEventListener('pageshow', done)
})
// callback
onPageShow(event => addWidget(user))
// promise
onPageShow().then(([event]) => addWidget(user))
when
provides a way to register listeners for a function call before or after
the function is called. This can be used to "pin" events (or other kinds of
signal/notification) so that they can be consumed after they've fired.
Several events and notifications that are exposed as transient, fire-and-forget messages are better thought of as states, e.g. "ready" notifications for services, or lifecycle events for web pages. They tend to be fired once, and we often need to detect that they've fired after the fact. This usage is not supported by most event-emitter implementations, and handling it manually can involve fiddly imperative code which obscures the simple semantics.
This module exports a function which allows these notifications to be pinned like "sticky" announcements on a message board or forum, rather than the blink-and-you-miss-it behavior of events, removing the timing-sensitivity that can make the event-based representation of these states inconvenient to use or unreliable.
when
provides a way to pin notifications when a framework or library doesn't
provide a way to do that itself, e.g. when consuming events produced by most
event-emitter implementations. But if you control/emit the notifications
yourself, and want consumers to be able to subscribe to them after they've been
published, this can be handled in the notifier itself, e.g. by using a library
with support for pinned events such as fixed-event or
ipc-event-emitter.
The following types are referenced in the descriptions below.
export type Callback = (done: <A extends any[]>(...args: A) => void) => void;
export type ErrorHandler = (error: any) => void;
export type Listener = <A extends any[]>(this: unknown, ...args: A) => void;
export interface Subscribe {
<A extends any[]>(): Promise<A & { this: unknown }>;
(listener: Listener): () => boolean;
};
- Type:
(callback: Callback, onError?: ErrorHandler) => Subscribe
import when from '@chocolatey/when'
const onPageShow = when(done => {
window.addEventListener('pageshow', done)
})
user.getData(url) // load data if not cached
onPageShow(event => addWidget(user))
when
passes a delegating function to a callback and returns a function which
registers listeners to be called when the delegate is called. Listeners are
passed the same arguments and this
value as the delegate and are executed
asynchronously. Listeners registered after the delegate has been called are
invoked immediately.
When the returned function is passed a listener, it returns a function which can be used to unregister the listener if it hasn't already been called. The function returns true if the listener was successfully unregistered, or false otherwise.
const unsubscribe = onPageShow(() => addWidget(user))
// ...
if (!user.loggedIn) {
unsubscribe()
}
If the listener is omitted, a promise is returned which is resolved with the array of arguments passed to the delegate.
onPageShow().then(([event]) => addWidget(user))
const [event] = await onPageShow()
The delegate's this
value can be accessed via the this
property on the
arguments array:
onPageShow().then(args => {
const [event] = args
const self = args.this
})
const { 0: event, this: self } = await onPageShow()
when
takes an optional error-handler which is passed any error which occurs
when a listener is invoked. If not supplied, listener errors are logged with
console.error
.
const onReady = when(
done => emitter.once('ready', done),
error => errors.push(error)
)
onReady(() => loadFile(path))
// errors.push(new Error("no such file..."))
The delegate function records its arguments and this
value the first time
it's called and subsequent calls are ignored, i.e. it behaves like it's only
called once. This makes it safe to use with events which may be emitted
multiple times, though arranging for the delegate to only be called once can
still be useful to avoid spamming it with redundant calls.
const onBeforeUnload = when(done => {
window.addEventListener('beforeunload', done, { once: true })
})
Note that the delegating function is void, i.e. there's no way to return a value from it, and no way to return a different value from it each time it's called (since it's only executed once). There's also no way to perform a side effect such as stopping the propagation of an event, since listeners are executed asynchronously. If any of these features are needed, they can be handled by wrapping the delegate, e.g.
const onReady = when(done => {
const wrapper = function (event) {
const result = handle(event) // side effects
done.call(this, event) // notify listeners (once)
return result // return value
}
emitter.on('ready', wrapper)
})
The following NPM scripts are available:
- build - compile the library for testing and save to the target directory
- build:doc - generate the README's TOC (table of contents)
- build:release - compile the library for release and save to the target directory
- clean - remove the target directory and its contents
- rebuild - clean the target directory and recompile the library
- test - recompile the library and run the test suite
- test:run - run the test suite
- typecheck - sanity check the library's type definitions
- Maintained Node.js versions and compatible browsers
- fixed-event - EventEmitter for one-time tasks
- ipc-event-emitter - an EventEmitter wrapper for IPC between parent and child processes with support for pinned events
- wl - whenable events implementation
1.2.0
Copyright © 2021 by chocolateboy.
This is free software; you can redistribute it and/or modify it under the terms of the MIT license.