PHASE 3 — Topic 10: Understanding Events — emit, on, and Custom Event Names

We already used emit and on in the last post to say hello. Now let's go deeper and understand the full power of events, since this is the core of everything you will build with Socket.IO.

The Basic Pattern

At its heart, Socket.IO communication always follows the same pattern:

  • One side sends an event using emit(eventName, data)
  • The other side listens for that event using on(eventName, callback)

    socket.emit("orderPlaced", { orderId: 101, item: "Pizza" });

    socket.on("orderPlaced", (data) => {
        console.log(data.orderId, data.item);
    });

You can send multiple pieces of data in one emit too, they just arrive as separate arguments:


    socket.emit("updateItem", "item-1", { name: "Updated Name" });

    socket.on("updateItem", (itemId, updatedData) => {
        console.log(itemId, updatedData);
    });

Naming Your Own Events

Socket.IO does not force any naming rules on you, you can name events almost anything you want. But a few reserved event names already have special meaning, and you should never use them for your own custom events:

  • connect
  • disconnect
  • connect_error
  • disconnecting
  • newListener
  • removeListener

Beyond these, everything else is yours to design. Good event names make a real difference once your app grows past a handful of events. A few practical conventions worth following:

  • Use camelCase, matching the rest of your TypeScript codebase: sendMessage, not send_message or SendMessage
  • Prefer clear, specific verbs over vague ones: userTyping is better than just typing
  • Group related events with a shared prefix when it helps: chat:message, chat:typing, chat:userJoined. This is optional, but useful once you have many event categories in a large app
  • Keep client-to-server and server-to-client events named differently when they represent different actions, don't reuse message for both a request and a response, it gets confusing fast

Whatever convention you pick, the important part is staying consistent across your whole project, since this directly maps to the ServerToClientEvents and ClientToServerEvents interfaces we set up earlier.

Acknowledgements: Getting a Response Back

Sometimes a plain emit is not enough, you want to know that the other side actually received and processed your event, almost like a normal function call with a return value. Socket.IO supports this using acknowledgements.

You do this by passing a callback function as the last argument of emit:


    socket.emit("updateItem", "item-1", { name: "Updated" }, (response) => {
        console.log(response.status);
    });

On the receiving side, that same callback becomes an extra argument in your on handler:


    socket.on("updateItem", (itemId, updatedData, callback) => {
        console.log(itemId, updatedData);
        callback({ status: "ok" });
    });

Once callback(...) is called on the receiving side, it triggers the function you passed on the sending side, with whatever data you passed into it. This gives you a proper request-response feel, on top of Socket.IO's normal event-based communication.

Adding a Timeout to Acknowledgements

Normally, if the other side never calls the acknowledgement callback, your emit's callback simply never fires, it just waits forever. You can protect against this using .timeout():


    socket.timeout(5000).emit("updateItem", "item-1", { name: "Updated" }, (err, response) => {
        if (err) {
            console.log("No response within 5 seconds");
        } else {
            console.log(response.status);
        }
    });

If no acknowledgement arrives within 5000 milliseconds, err will be set, letting you handle the failure case properly instead of waiting indefinitely.

Volatile Events

Sometimes you don't actually care if an event gets lost, for example, sending a player's live position in a fast-paced game, where only the latest position matters anyway. For cases like this, Socket.IO offers volatile emits:


    socket.volatile.emit("playerPosition", { x: 120, y: 340 });

A volatile event is simply dropped if the connection is not ready to send it at that exact moment, instead of being queued up and sent later. This avoids flooding the connection with outdated data once it reconnects.

Listening to Every Event: onAny

For debugging, or for building generic logging systems, Socket.IO also lets you listen to every incoming event at once, without specifying a name:


    socket.onAny((eventName, ...args) => {
        console.log("Received event:", eventName, args);
    });

This is very useful during development, since you can quickly see everything flowing through your connection without adding a separate listener for each event manually. Just remember to remove it in production code once you're done debugging, using socket.offAny().

Putting It Together: A Small Realistic Example

Let's say you're building a "mark item as favorite" feature. Here is how emit, on, and acknowledgements might work together for it, add this to your existing src/types/socket.ts:


    export interface ClientToServerEvents {
        sendMessage: (data: { text: string; sender: string }) => void;
        sayHello: () => void;
        toggleFavorite: (
            itemId: string,
            callback: (response: { success: boolean }) => void
        ) => void;
    }

Server side, inside server.ts:


    socket.on("toggleFavorite", (itemId, callback) => {
        console.log("Toggling favorite for item:", itemId);
        callback({ success: true });
    });

Client side, wherever you handle the button click:


    socket.emit("toggleFavorite", "item-42", (response) => {
        if (response.success) {
            console.log("Favorite toggled successfully");
        }
    });

Notice how this feels almost like calling a normal async function, even though it's fully event-driven underneath.

Summary

  • emit sends an event with a name and optional data, on listens for that same event name
  • Custom event names are entirely up to you, but avoid Socket.IO's reserved names like connect and disconnect
  • Acknowledgements let you get a response back from the other side, just like a request-response call
  • .timeout() protects your acknowledgements from waiting forever if no response comes
  • Volatile emits are useful when losing an occasional event is acceptable, such as fast-changing position data
  • onAny is a handy debugging tool to see every event flowing through your connection

In the next post, we go further into sending data both ways, covering more real patterns for client-to-server and server-to-client communication in a typical application.

No comments:

Post a Comment

PHASE 4 — Topic 13: Building a Simple Real-Time Chat Application (UI with Tailwind)

Time to put everything from Phase 3 together into one complete, working feature: a real chat application. This post combines connection hand...