It's sometimes surprising how just few lines of codes can be very usefull
/* ------------------------------
EventBus
------------------------------ */
class EventBus {
constructor() {
this.listeners = new Map()
}
on(event, handler) {
let arr = this.listeners.get(event)
if (arr) arr.push(handler)
else this.listeners.set(event, [handler])
return this
}
emit(event, data) {
const arr = this.listeners.get(event)
if (!arr) return
const copy = arr.slice()
for (let fn of copy) fn(data)
}
}
const bus = new EventBus()
/* ------------------------------
UI Elements
------------------------------ */
const avatar = document.getElementById("avatar")
const nameEl = document.getElementById("name")
const emailEl = document.getElementById("email")
const locEl = document.getElementById("location")
const statusEl = document.getElementById("status")
const btn = document.getElementById("fetchBtn")
/* ------------------------------
Fetch Random User
------------------------------ */
async function fetchRandomUser() {
statusEl.innerHTML = "<div class='loading'>Loading user...</div>"
try {
const res = await fetch("https://randomuser.me/api/")
if (!res.ok) throw new Error("Network error")
const data = await res.json()
const user = data.results[0]
bus.emit("user.loaded", user)
} catch (err) {
bus.emit("user.error", err)
}
}
/* ------------------------------
Event Handlers
------------------------------ */
// When user is loaded
bus.on("user.loaded", (user) => {
statusEl.innerHTML = ""
avatar.src = user.picture.large
avatar.hidden = false
nameEl.textContent = `${user.name.first} ${user.name.last}`
emailEl.textContent = user.email
locEl.textContent = `${user.location.city}, ${user.location.country}`
})
// On error
bus.on("user.error", (err) => {
statusEl.innerHTML = `<div class='error'>Error: ${err.message}</div>`
})
/* ------------------------------
Button action
------------------------------ */
btn.addEventListener("click", fetchRandomUser)
// Auto-load on startup
fetchRandomUser()
You can test the demo here : EventBus
Here is another version with State Managment :
/* ------------------------------
EventBus (For Action/Flow Events)
------------------------------ */
class EventBus {
constructor() {
this.listeners = new Map()
}
on(event, handler) {
let arr = this.listeners.get(event)
if (arr) arr.push(handler)
else this.listeners.set(event, [handler])
return this
}
emit(event, data) {
const arr = this.listeners.get(event)
if (!arr) return
const copy = arr.slice()
for (let fn of copy) fn(data)
}
}
const bus = new EventBus()
/* ------------------------------
StateManager (For Data State)
------------------------------ */
class StateManager {
constructor(initialState) {
this.state = initialState;
this.listeners = []; // Functions to call when state changes
}
getState() {
return this.state;
}
// Updates the state and notifies all subscribers
setState(newState) {
this.state = { ...this.state,
...newState
}; // Merge new state with existing
this.notifyListeners();
}
// Adds a function to be called on state changes
subscribe(listener) {
this.listeners.push(listener);
// Immediately call the listener with the current state for initial render
listener(this.state);
}
// Notifies all subscribed listeners with the current state
notifyListeners() {
for (const listener of this.listeners) {
listener(this.state);
}
}
}
// Initialize the state manager with an initial state
const stateManager = new StateManager({
user: null,
loading: false,
error: null,
});
/* ------------------------------
UI Elements
------------------------------ */
const avatar = document.getElementById("avatar")
const nameEl = document.getElementById("name")
const emailEl = document.getElementById("email")
const locEl = document.getElementById("location")
const statusEl = document.getElementById("status")
const btn = document.getElementById("fetchBtn")
/* ------------------------------
Fetch Random User
------------------------------ */
async function fetchRandomUser() {
// 1. **ACTION:** Inform the world that the loading process is starting
bus.emit("fetch.start");
try {
const res = await fetch("https://randomuser.me/api/")
if (!res.ok) throw new Error("Network error")
const data = await res.json()
const user = data.results[0]
// 2. **ACTION:** Inform the world that the user loaded successfully
bus.emit("user.loaded", user)
} catch (err) {
// 2. **ACTION:** Inform the world that an error occurred
bus.emit("user.error", err)
}
}
/* ------------------------------
Event Handlers (Update State)
------------------------------ */
// When fetch starts
bus.on("fetch.start", () => {
// Set the central state for loading
stateManager.setState({
loading: true,
error: null,
user: null
});
});
// When user is loaded
bus.on("user.loaded", (user) => {
// Set the central state with the new user data
stateManager.setState({
user: user,
loading: false,
error: null
});
});
// On error
bus.on("user.error", (err) => {
// Set the central state with the error
stateManager.setState({
error: err,
loading: false,
user: null
});
});
/* ------------------------------
UI Update Function (Reacts to state changes)
------------------------------ */
// This function acts as the single listener to the StateManager
function updateUI(state) {
// Handle loading status
if (state.loading) {
statusEl.innerHTML = "<div class='loading'>Loading user...</div>";
avatar.hidden = true;
nameEl.textContent = "";
emailEl.textContent = "";
locEl.textContent = "";
return;
}
// Handle errors
if (state.error) {
statusEl.innerHTML = `<div class='error'>Error: ${state.error.message}</div>`;
avatar.hidden = true;
nameEl.textContent = "";
emailEl.textContent = "";
locEl.textContent = "";
return;
}
// Handle user data display
if (state.user) {
statusEl.innerHTML = "";
avatar.src = state.user.picture.large;
avatar.hidden = false;
nameEl.textContent = `${state.user.name.first} ${state.user.name.last}`;
emailEl.textContent = state.user.email;
locEl.textContent = `${state.user.location.city}, ${state.user.location.country}`;
} else {
// If no user and not loading/error, clear everything (e.g., initial state)
statusEl.innerHTML = "";
avatar.hidden = true;
nameEl.textContent = "";
emailEl.textContent = "";
locEl.textContent = "";
}
}
/* ------------------------------
Button action & State Subscription
------------------------------ */
btn.addEventListener("click", fetchRandomUser)
// The UI is the ONLY subscriber to the state manager
stateManager.subscribe(updateUI);
// Auto-load on startup
fetchRandomUser()
Top comments (0)