イベントのリスニング
サーバーとクライアント間で送信されるイベントを処理するには、いくつかの方法があります。
EventEmitter メソッド
サーバー側では、Socket インスタンスは Node.js の EventEmitter クラスを拡張します。
クライアント側では、Socket インスタンスは component-emitter ライブラリによって提供されるイベントエミッターを使用します。これは EventEmitter メソッドのサブセットを公開します。
socket.on(eventName, listener)
eventName という名前のイベントのリスナー配列の末尾に listener 関数を追加します。
socket.on("details", (...args) => {
  // ...
});
socket.once(eventName, listener)
eventName という名前のイベントの **1回限り** の listener 関数を追加します。
socket.once("details", (...args) => {
  // ...
});
socket.off(eventName, listener)
eventName という名前のイベントのリスナー配列から指定された listener を削除します。
const listener = (...args) => {
  console.log(args);
}
socket.on("details", listener);
// and then later...
socket.off("details", listener);
socket.removeAllListeners([eventName])
すべてのリスナー、または指定された eventName のリスナーを削除します。
// for a specific event
socket.removeAllListeners("details");
// for all events
socket.removeAllListeners();
キャッチオールリスナー
Socket.IO v3 以降、EventEmitter2 ライブラリに触発された新しい API により、キャッチオールリスナーを宣言できるようになりました。
この機能は、クライアントとサーバーの両方で使用できます。
socket.onAny(listener)
任意のイベントが発行されたときに起動されるリスナーを追加します。
socket.onAny((eventName, ...args) => {
  // ...
});
確認応答 は、キャッチオールリスナーではキャッチされません。
socket.emit("foo", (value) => {
  // ...
});
socket.onAnyOutgoing(() => {
  // triggered when the event is sent
});
socket.onAny(() => {
  // not triggered when the acknowledgement is received
});
socket.prependAny(listener)
任意のイベントが発行されたときに起動されるリスナーを追加します。リスナーは、リスナー配列の先頭に追加されます。
socket.prependAny((eventName, ...args) => {
  // ...
});
socket.offAny([listener])
すべてのキャッチオールリスナー、または指定されたリスナーを削除します。
const listener = (eventName, ...args) => {
  console.log(eventName, args);
}
socket.onAny(listener);
// and then later...
socket.offAny(listener);
// or all listeners
socket.offAny();
socket.onAnyOutgoing(listener)
送信パケットの新しいキャッチオールリスナーを登録します。
socket.onAnyOutgoing((event, ...args) => {
  // ...
});
確認応答 は、キャッチオールリスナーではキャッチされません。
socket.on("foo", (value, callback) => {
  callback("OK");
});
socket.onAny(() => {
  // triggered when the event is received
});
socket.onAnyOutgoing(() => {
  // not triggered when the acknowledgement is sent
});
socket.prependAnyOutgoing(listener)
送信パケットの新しいキャッチオールリスナーを登録します。リスナーは、リスナー配列の先頭に追加されます。
socket.prependAnyOutgoing((event, ...args) => {
  // ...
});
socket.offAnyOutgoing([listener])
以前に登録されたリスナーを削除します。リスナーが指定されていない場合、すべてのキャッチオールリスナーが削除されます。
const listener = (eventName, ...args) => {
  console.log(eventName, args);
}
socket.onAnyOutgoing(listener);
// remove a single listener
socket.offAnyOutgoing(listener);
// remove all listeners
socket.offAnyOutgoing();
検証
イベント引数の検証は、Socket.IO ライブラリの範囲外です。
JS エコシステムには、このユースケースに対応する多くのパッケージがあります。その中には
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: "Bad Request",
        error
      });
    }
    // do something with the value, and then
    callback({
      status: "OK"
    });
  });
});
エラー処理
現在、Socket.IO ライブラリには組み込みのエラー処理がありません。つまり、リスナーでスローされる可能性のあるエラーをキャッチする必要があります。
io.on("connection", (socket) => {
  socket.on("list items", async (callback) => {
    try {
      const items = await findItems();
      callback({
        status: "OK",
        items
      });
    } catch (e) {
      callback({
        status: "NOK"
      });
    }
  });
});
これは以下のようにリファクタリングできます。
const errorHandler = (handler) => {
  const handleError = (err) => {
    console.error("please handle me", err);
  };
  return (...args) => {
    try {
      const ret = handler.apply(this, args);
      if (ret && typeof ret.catch === "function") {
        // async handler
        ret.catch(handleError);
      }
    } catch (e) {
      // sync handler
      handleError(e);
    }
  };
};
// server or client side
socket.on("hello", errorHandler(() => {
  throw new Error("let's panic");
}));