npm add yieldmachine
start(machineDefinition: GeneratorFunction)
Starts a machine, transitioning to its initially returned state.
.current: string | Record<string, unknown>
The current state of the machine. If machines were nested then an object is returned with the parent machine as the key, and its current state as the value.
.changeCount: number
The number of times this machine has transitioned. Useful for consumers updating only when changes have been made.
.results: Promise<unknown>
The result of any entry()
or exit()
messages.
.next(eventName: string | symbol)
Sends an event to the machine, transitioning if the event was recognised. Unrecognised events are ignored.
.stop()
Cleans up the machine.
on(eventName: string | symbol, target: GeneratorFunction)
Transitions to the target state when the given event occurs.
enter(action: () => undefined | unknown | Promise<unknown>)
Runs the provided function when this state is entered. If the function returns a promise, its value is made available in the .results
property of the machine, keyed by the name of this passed function.
exit(action: () => undefined | unknown | Promise<unknown>)
Runs the provided function when this state is exited.
cond(predicate: () => boolean, target: GeneratorFunction)
Immediately transitions to the target state if the provided predicate function returns true
.
always(target: GeneratorFunction)
Immediately transitions to the target state, if previous cond()
did not pass.
listenTo(sender: EventTarget, eventName: string)
Listens to an EventTarget
ā for example, an HTMLElement like a button.
Uses .addEventListener()
to listen to the event. The listener is removed when transitioning to a different state or when the machine is stopped, so no extra clean up is necessary.
function ButtonClickListener(button: HTMLButtonElement) {
function* initial() {
yield on("click", clicked);
yield listenTo(button, "click");
}
function* clicked() {}
return initial;
}
const button = document.createElement('button');
const machine = start(ButtonClickListener.bind(null, button));
machine.current; // "initial"
button.click();
machine.current; // "clicked"
button.click();
machine.current; // "clicked"
import { entry, on, start } from "yieldmachine";
const exampleURL = new URL("https://example.org/");
function fetchData() {
return fetch(exampleURL);
}
// Define a machine just using functions
function Loader() {
// Each state is a generator function
function* idle() {
yield on("FETCH", loading);
}
// This is the āloadingā state
function* loading() {
// This function will be called when this state is entered.
// Its return value is available at `loader.results.fetchData`
yield entry(fetchData);
// If the promise succeeds, we will transition to the `success` state
// If the promise fails, we will transition to the `failure` state
yield on("SUCCESS", success);
yield on("FAILURE", failure);
}
// States that donāt yield anything are final
function* success() {}
// Or they can define transitions to other states
function* failure() {
// When the RETRY event happens, we transition from āfailureā to āloadingā
yield on("RETRY", loading);
}
// Return the initial state from your machine definition
return idle;
}
const loader = start(Loader);
loader.current; // "idle"
loader.next("FETCH");
loader.current; // "loading"
loader.results.then((results) => {
console.log("Fetched", results.fetchData); // Use response of fetch()
loader.current; // "success"
});
/* Or with await: */
// const { fetchData } = await loader.results;
// loader.current; // "success"
import { entry, on, start } from "yieldmachine";
// Function taking as many arguments as you like
function GenericLoader(url) {
function fetchData() {
return fetch(url);
}
function* idle() {
yield on("FETCH", loading);
}
function* loading() {
yield entry(fetchData);
yield on("SUCCESS", success);
yield on("FAILURE", failure);
}
function* success() {}
function* failure() {
yield on("RETRY", loading);
}
return idle;
}
// Function taking no arguments that will define our machine
function SpecificLoader() {
const exampleURL = new URL("https://example.org/");
return GenericLoader(exampleURL);
}
// Start our specific loader machine
const loader = start(SpecificLoader);
loader.current; // "idle"
loader.next("FETCH");
loader.current; // "loading"
loader.results.then((results) => {
console.log("Fetched", results.fetchData); // Use response of fetch()
loader.current; // "success"
});
AbortController
wrapperfunction AbortListener(controller: AbortController) {
function* initial() {
if (controller.signal.aborted) {
yield always(aborted);
} else {
yield on("abort", aborted);
yield listenTo(controller.signal, "abort");
}
}
function* aborted() {}
return initial;
}
const aborter = new AbortController();
const machine = start(AbortListener.bind(null, aborter));
machine.current; // "initial"
aborter.abort();
machine.current; // "aborted"
Event | { type: string }
Further reading / inspiration: