promiventerator
v0.1.4
Published
A Promise that's also an async EventEmitter that's also an AsyncIterable. Fully typed, and cancelable. Lets a single function return a result that allows the caller to await the final result, listen for detailed progress updates, stream incremental progre
Readme
Promiventerator
A Promiventerator is what you'd get if a Promise, an AsyncEventEmitter, and an AsyncIterator got together and decided to have a baby. Handy for when you want to let the caller decide whether they just want the final result, whether they want to listen to all the events that happen along the way, or whether they want to stream events in a for await loop. Fully typed, too.
Features
- Fully Promise-compatible
- Typed event emission and handling
- AsyncIterator interface for event streams
- Complete event history for new subscribers, with configurable buffering
- Full TypeScript support
- Zero dependencies
Installation
npm install promiventeratorUsage
Complete Example
import { Promiventerator } from "promiventerator";
// Describe your events: keys are event names, values are payload types.
// Payloads can be any type; use `void` for events that carry no payload.
interface MyEvents {
progress: number;
data: { value: string };
complete: void;
}
// The executor emits events while it works, then resolves like any Promise
const pv = new Promiventerator<string, MyEvents>((resolve) => {
setTimeout(() => pv.emit("progress", 50), 500);
setTimeout(() => pv.emit("data", { value: "hello" }), 750);
setTimeout(() => {
pv.emit("complete");
resolve("done");
}, 1000);
});
// Listen to events
pv.on("progress", (value) => console.log(`event: progress ${value}`));
pv.on("data", ({ value }) => console.log(`event: data ${value}`));
pv.on("complete", () => console.log("event: complete"));
// Stream events with for await; past events are replayed first, and the
// loop ends when the promise resolves
for await (const event of pv) {
const [eventName, data] = event;
if (eventName === "progress") {
console.log(`for loop: ${eventName} ${data}`);
} else if (eventName === "data") {
console.log(`for loop: ${eventName} ${data.value}`);
} else {
console.log(`for loop: ${eventName}`);
}
}
// Wait for the final result
console.log(await pv);
// Output:
// event: progress 50
// for loop: progress 50
// event: data hello
// for loop: data hello
// event: complete
// for loop: complete
// doneAPI
Constructor
new Promiventerator<T, Events>(executor: (resolve, reject) => void, options?: PromiventeratorOptions)T: The type of the Promise resultEvents: An interface describing your event types
Options
buffer?: boolean | number— controls how emitted events are buffered for replay to iterators created after the events were emitted:true(default): buffer every event; new iterators replay the full historyfalse: buffer nothing; iterators only see events emitted after they are created- a non-negative integer: buffer at most that many of the most recent events
// Keep only the 10 most recent events for late subscribers const pv = new Promiventerator<string, MyEvents>(executor, { buffer: 10 });consume?: boolean— controls whether buffered events are removed from the buffer once they are consumed:false(default): buffered events are retained, so every new iterator replays the full buffered historytrue: an event is removed from the buffer as soon as any iterator yields it, so iterators created later only replay events that have not yet been consumed
This only affects replay to iterators created after the fact; iterators that exist when an event is emitted always receive it, and
on/oncelisteners are unaffected.// Each buffered event is replayed at most once const pv = new Promiventerator<string, MyEvents>(executor, { consume: true });
Methods
.on<K>(eventName: K, handler: EventReceiver<Events[K]>): this- Add an event listener
- Returns
thisfor chaining
.once<K>(eventName: K, handler: EventReceiver<Events[K]>): this- Add a one-time event listener
- Returns
thisfor chaining
.off<K>(eventName: K, handler: EventReceiver<Events[K]>): this- Remove an event listener
- Returns
thisfor chaining
.emit<K>(eventName: K, data?: Events[K]): Promise<boolean>- Emit an event with optional data
- Returns a Promise that resolves to
trueif there were listeners
[Symbol.asyncIterator](): AsyncIterator<[EventKey<Events>, Events[EventKey<Events>]] | [EventKey<Events>]>- Returns an AsyncIterator for the event stream
- Includes buffered historical events (all of them by default; see the
bufferconstructor option)
Feel free to contribute!
