๐ง The Power of the when() Method
๐ง Bridging Sync and Async Domains — The Power of the when()
Method
In modern JavaScript, the divide between synchronous values and asynchronous events often forces developers to juggle Promises, callbacks, and event handlers.
The when()
method provides a unifying abstraction, allowing us to handle Promises, event-driven flows, and distributed interactions as if they were synchronous, declarative chains of logic.
๐ Iterables of Promises
Calling when()
on an iterable of Promises converts it into an AsyncEach
of resolved values. This means you can process asynchronous data in the same fluent, composable way you’d handle synchronous data.
Example
import { Each } from '@fizzwiz/fluent';
Each.of(Promise.resolve(1), Promise.resolve(2), Promise.resolve(3))
.when() // Converts Each<Promise<number>> -> AsyncEach<number>
.sthen(n => n * 2) // Process numbers
.sthen(console.log); // 2, 4, 6
Here, when()
bridges the gap: promises are seamlessly turned into real values, and the rest of the chain remains clean and synchronous in appearance.
⚙️ Functions and Workflows
On a What
, calling:
.when(predicate, emitter, event, timeoutMs)
creates an AsyncWhat
that internally manages attaching/detaching listeners to the given emitter.
This makes it possible to write event-driven workflows declaratively, as if they were ordinary synchronous chains:
a.sthen(b)
but where b
doesn’t just consume the output of a
—instead, it consumes an event triggered as a consequence of a
.
๐ก Event-Driven Asynchronous Flows
Let’s explore the request–response pattern over WebSockets.
Instead of callbacks and state management, we describe the workflow in a natural, sequential style:
request.sthen(handleResponse.when(isResponse, ws, 'message', timeoutMs))
Example: Request–Response with Timeout
// Assume an open WebSocket connection
const ws = new WebSocket("wss://chat.example.com");
const requestId = "123";
// Step 1: message sender with a unique requestId
const sendMessage = () =>
ws.send(JSON.stringify({
user: "Me",
requestId,
message: "Hello Alice!"
}));
// Step 2: reply handler
const handleReply = What.as(evt => {
const msg = JSON.parse(evt.data);
console.log("๐ฌ Got reply:", msg.message);
});
// Step 3: predicate to select Alice’s response by requestId
const isResponse = evt => {
const msg = JSON.parse(evt.data);
return msg.user === "Alice" && msg.requestId === requestId;
};
// Step 4: wait for Alice’s reply, within 5s
const waitForAlice = handleReply
.when(isResponse, ws, "message", 5000)
.else(() => console.log("⏰ Alice didn’t reply in time"), "TimeoutError")
.else(() => console.log("⚠️ Connection error"));
// Step 5: compose and execute request–response workflow
await AsyncWhat.as(sendMessage) // lift sync function into AsyncWhat
.sthen(waitForAlice)(); // async chain
// Outcomes:
// ✅ If Alice replies → "๐ฌ Got reply: Hi!"
// ⏰ If timeout → "⏰ Alice didn’t reply in time"
// ⚠️ If socket errors → "⚠️ Connection error"
Instead of wiring handlers manually, we describe intent: send a message, then wait for a matching reply, with timeouts and fallbacks.
๐ Why This Matters
when()
is more than syntactic sugar—it’s a way to abstract over time, events, and distributed endpoints:
- Promises / async values → unified as
AsyncEach
. - Event-driven workflows → declaratively modeled with
AsyncWhat
. - Distributed systems → logic can span across sockets, services, or machines while still reading like a local synchronous chain.
✅ Conclusion
With when()
, asynchronous programming becomes fluent, composable, and natural.
It lets us express complex interactions—timeouts, retries, request/response patterns, distributed messaging—while keeping the business logic front and center.
Write code that reads like thought—whether it’s synchronous, asynchronous, or happening across the network.
Comments
Post a Comment