Listening to events
There are several ways to handle events that are transmitted between the server and the client.
#
EventEmitter methodsOn the server-side, the Socket instance extends the Node.js EventEmitter class.
On the client-side, the Socket instance uses the event emitter provided by the component-emitter library, which exposes a subset of the EventEmitter methods.
#
socket.on(eventName, listener)Adds the listener function to the end of the listeners array for the event named eventName.
socket.on("details", (...args) => { // ...});
#
socket.once(eventName, listener)Adds a one-time listener function for the event named eventName
socket.once("details", (...args) => { // ...});
#
socket.off(eventName, listener)Removes the specified listener from the listener array for the event named eventName.
const listener = (...args) => { console.log(args);}
socket.on("details", listener);
// and then later...socket.off("details", listener);
#
socket.removeAllListeners([eventName])Removes all listeners, or those of the specified eventName.
// for a specific eventsocket.removeAllListeners("details");// for all eventssocket.removeAllListeners();
#
Catch-all listenersSince Socket.IO v3, a new API inspired from the EventEmitter2 library allows to declare catch-all listeners.
This feature is available on both the client and the server.
#
socket.onAny(listener)Adds a listener that will be fired when any event is emitted.
socket.onAny((eventName, ...args) => { // ...});
#
socket.prependAny(listener)Adds a listener that will be fired when any event is emitted. The listener is added to the beginning of the listeners array.
socket.prependAny((eventName, ...args) => { // ...});
#
socket.offAny([listener])Removes all catch-all listeners, or the given listener.
const listener = (eventName, ...args) => { console.log(eventName, args);}
socket.onAny(listener);
// and then later...socket.offAny(listener);
// or all listenerssocket.offAny();
#
ValidationThe validation of the event arguments is out of the scope of the Socket.IO library.
There are many packages in the JS ecosystem which cover this use case, among them:
Example with joi and acknowledgements:
const Joi = require("joi");
const userSchema = Joi.object({ username: Joi.string().max(30).required(), email: Joi.string().email().required()});
io.on("connection", (socket) => { socket.on("create user", (payload, callback) => { if (typeof callback !== "function") { // not an acknowledgement return socket.disconnect(); } const { error, value } = userSchema.validate(payload); if (error) { return callback({ status: "KO", error }); } // do something with the value, and then callback({ status: "OK" }); });
});
#
Error handlingThere is currently no built-in error handling in the Socket.IO library, which means you must catch any error that could be thrown in a listener.
io.on("connection", (socket) => { socket.on("list items", async (callback) => { try { const items = await findItems(); callback({ status: "OK", items }); } catch (e) { callback({ status: "NOK" }); } });});
On the server-side, using EventEmitter.captureRejections = true
(experimental, see here) might be interesting too, depending on your use case.
require("events").captureRejections = true;
io.on("connection", (socket) => { socket.on("list products", async () => { const products = await findProducts(); socket.emit("products", products); });
socket[Symbol.for('nodejs.rejection')] = (err) => { socket.emit("error", err); };});