Escuchar eventos
Hay varias formas de manejar eventos que se transmiten entre el servidor y el cliente.
Métodos de EventEmitter
En el lado del servidor, la instancia Socket extiende la clase EventEmitter de Node.js.
En el lado del cliente, la instancia Socket usa el emisor de eventos proporcionado por la biblioteca component-emitter, que expone un subconjunto de los métodos de EventEmitter.
socket.on(eventName, listener)
Agrega la función listener al final del array de listeners para el evento llamado eventName.
socket.on("details", (...args) => {
// ...
});
socket.once(eventName, listener)
Agrega una función listener de una sola vez para el evento llamado eventName
socket.once("details", (...args) => {
// ...
});
socket.off(eventName, listener)
Elimina el listener especificado del array de listeners para el evento llamado eventName.
const listener = (...args) => {
console.log(args);
}
socket.on("details", listener);
// y luego más tarde...
socket.off("details", listener);
socket.removeAllListeners([eventName])
Elimina todos los listeners, o aquellos del eventName especificado.
// para un evento específico
socket.removeAllListeners("details");
// para todos los eventos
socket.removeAllListeners();
Listeners catch-all
Desde Socket.IO v3, una nueva API inspirada en la biblioteca EventEmitter2 permite declarar listeners catch-all.
Esta característica está disponible tanto en el cliente como en el servidor.
socket.onAny(listener)
Agrega un listener que será disparado cuando cualquier evento sea emitido.
socket.onAny((eventName, ...args) => {
// ...
});
Los Acknowledgements no son capturados en el listener catch-all.
socket.emit("foo", (value) => {
// ...
});
socket.onAnyOutgoing(() => {
// se activa cuando el evento es enviado
});
socket.onAny(() => {
// no se activa cuando se recibe el acknowledgement
});
socket.prependAny(listener)
Agrega un listener que será disparado cuando cualquier evento sea emitido. El listener se agrega al principio del array de listeners.
socket.prependAny((eventName, ...args) => {
// ...
});
socket.offAny([listener])
Elimina todos los listeners catch-all, o el listener dado.
const listener = (eventName, ...args) => {
console.log(eventName, args);
}
socket.onAny(listener);
// y luego más tarde...
socket.offAny(listener);
// o todos los listeners
socket.offAny();
socket.onAnyOutgoing(listener)
Registra un nuevo listener catch-all para paquetes salientes.
socket.onAnyOutgoing((event, ...args) => {
// ...
});
Los Acknowledgements no son capturados en el listener catch-all.
socket.on("foo", (value, callback) => {
callback("OK");
});
socket.onAny(() => {
// se activa cuando el evento es recibido
});
socket.onAnyOutgoing(() => {
// no se activa cuando el acknowledgement es enviado
});
socket.prependAnyOutgoing(listener)
Registra un nuevo listener catch-all para paquetes salientes. El listener se agrega al principio del array de listeners.
socket.prependAnyOutgoing((event, ...args) => {
// ...
});
socket.offAnyOutgoing([listener])
Elimina el listener previamente registrado. Si no se proporciona un listener, se eliminan todos los listeners catch-all.
const listener = (eventName, ...args) => {
console.log(eventName, args);
}
socket.onAnyOutgoing(listener);
// eliminar un solo listener
socket.offAnyOutgoing(listener);
// eliminar todos los listeners
socket.offAnyOutgoing();
Validación
La validación de los argumentos de eventos está fuera del alcance de la biblioteca Socket.IO.
Hay muchos paquetes en el ecosistema JS que cubren este caso de uso, entre ellos:
Ejemplo con joi y 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") {
// no es un acknowledgement
return socket.disconnect();
}
const { error, value } = userSchema.validate(payload);
if (error) {
return callback({
status: "Bad Request",
error
});
}
// hacer algo con el valor, y luego
callback({
status: "OK"
});
});
});
Manejo de errores
Actualmente no hay manejo de errores incorporado en la biblioteca Socket.IO, lo que significa que debes capturar cualquier error que pueda lanzarse en un 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"
});
}
});
});
Esto puede ser refactorizado en:
const errorHandler = (handler) => {
const handleError = (err) => {
console.error("por favor manéjame", err);
};
return (...args) => {
try {
const ret = handler.apply(this, args);
if (ret && typeof ret.catch === "function") {
// handler asíncrono
ret.catch(handleError);
}
} catch (e) {
// handler síncrono
handleError(e);
}
};
};
// lado del servidor o cliente
socket.on("hello", errorHandler(() => {
throw new Error("entremos en pánico");
}));