๐ง The Power of the when() Method
๐ง Bridging Sync and Async Domains — The Power of when()
In modern JavaScript, we often juggle two different worlds:
- The synchronous world of values, arrays, and functions
- The asynchronous world of Promises, events, and distributed systems
Bridging these domains usually means boilerplate: callbacks, event listeners, timeouts, and manual cleanup.
The when() method provides a unifying abstraction. It makes synchronous and asynchronous flows feel the same—expressed as declarative, chainable logic rather than tangled callbacks.
At its core, when() brings two powers:
Start/stop control
- In iterations: slice infinite/lazy sequences by event-like conditions.
- In workflows: start or stop tasks dynamically on external triggers.
Domain promotion
- Iterables of promises → seamlessly promoted into async iterables of resolved values.
- Synchronous tasks → promoted into asynchronous workflows that respond to events.
๐ Iterables: start/stop by predicate
On an iterable, calling when(predicate, start) lets you lazily restrict iteration—no need to precompute everything.
const slice = Each.NATURAL
.when(n => n == 3, true) // Start at n = 3
.when(n => n == 5, false); // Stop at n = 5
console.log([...slice]); // -> [3, 4]
Just like event handlers gate execution, when() gates iteration.
๐ Iterables of Promises: turning async into sync-like
On an iterable of Promises, when() promotes it to an AsyncEach of resolved values. Now async values behave just like sync ones:
Each.of(Promise.resolve(1), Promise.resolve(2), Promise.resolve(3))
.when() // Each<Promise<number>> → AsyncEach<number>
.sthen(n => n * 2) // Process numbers as if they were sync
The gap between sync and async vanishes—you write logic once, and it works across both.
⚙️ Functions and Workflows: events as first-class steps
For functions, when(predicate, emitter, event, timeoutMs) promotes a What into an AsyncWhat. Internally, it manages event listeners, timeouts, and cancellation—but you just chain it declaratively.
This lets you treat events the same way you treat function outputs:
a.sthen(b);
However, with when(), b can wait for an event triggered by a.
a.sthen(b.when(predicate, emitter, event));
Here b doesn’t just consume a’s result. It consumes the consequence of the a action.
๐ก Event-Driven Flows: request–response made effortless
The power of when() really shines in a typical request–response pattern.
Instead of manually wiring event listeners, handling timeouts, or managing cleanup, you can express the workflow declaratively and sequentially, as if it were synchronous:
request.sthen(handleResponse.when(isResponse, ws, "message", 5000));
Here, handleResponse will automatically wait for the event matching isResponse, respecting the 5-second timeout—all without additional boilerplate.
This approach lets you focus on the logic, not the plumbing.
Example
const ws = new WebSocket("wss://chat.example.com");
const requestId = "123";
// Send a request
const sendMessage = () =>
ws.send(JSON.stringify({ user: "Me", requestId, message: "Hello Alice!" }));
// Handle Alice’s reply
const handleResponse = What.as(evt => {
const msg = JSON.parse(evt.data);
console.log("๐ฌ Got reply:", msg.message);
});
// Select exactly Alice’s response
const isResponse = evt => {
const msg = JSON.parse(evt.data);
return msg.user === "Alice" && msg.requestId === requestId;
};
// Compose the workflow
const waitForAlice = handleResponse
.when(isResponse, ws, "message", 5000)
.else(() => console.log("⏰ Timeout"), "TimeoutError")
.else(() => console.log("⚠️ Connection error"));
await AsyncWhat.as(sendMessage).sthen(waitForAlice)();
Readable. Declarative. Intent-driven.
⏳ Periodic tasks: start/stop on demand
when() also lets you control recurring tasks the same way you slice sequences:
task.self(Infinity, 1000, 1) // repeat forever, every 1000ms
.when(isStart, emitter, event, undefined, true) // start when predicate matches
.when(isEnd, emitter, event, undefined, false); // stop when predicate matches
Just like with iterables, true means begin here, false means end here.
๐ Why It Matters
when() is not just sugar. It’s an abstraction over time, events, and distribution:
- Promises → values:
Each<Promise<T>>→AsyncEach<T> - Tasks → workflows:
What→AsyncWhat - Events → chains: external signals seamlessly embedded in declarative pipelines
- Distributed systems: chains can span machines yet still read like local logic
✅ Conclusion
With when(), asynchronous programming becomes:
- Fluent — you write in chains, not callbacks
- Composable — async, sync, and events mix freely
- Natural — the code reads like thought
๐ Try when() in your next async workflow and experience the difference.
— @fizzwiz ✨
Comments
Post a Comment