🐢

[js] 手続き的 な WebSocket ラッパーをつくろう

2024/02/17に公開

^.,.^ < Hey! 青色きつねです。
VSCode拡張機能のために書いたはいいものの結局使わなかった。せっかくなのでブログのネタにしちゃう。
websocketは現状イベントハンドラを使って処理を組んでいくので、例えば以下のように手続き的な書き方が出来ません。

socket.send("message");
let buf = socket.recv();

要はこう書きたくてawaitするモジュールを書きました。
特に目新しいことは何もないんですが、promiseの勉強の参考になるかもしれません。
イベント駆動型を手続き型にラップしたり、その逆もというのはpromiseの登場で容易になりました。らしい。(コールバック地獄の回避のほうが話題ですが)そんな現代を生きる野生動物はそんな乱暴な事をたまにやるので、メモとして残しておきます。

ソース

使い方

let awaitbleWebSocket = await (new AwaitbleWebSocket("ws:127.0.0.1:5001"));
let response = await s.send("Bello");
// { message: 'Bello', uuid: 'af6ec486-9c81-4e7b-b42a-0ea4950bac38' }

クライアント:
https://github.com/xoFeulB/BlueFox/blob/main/js/websocket.awaitable.js

サーバー:

const ws = require("ws");
let WebSocketServer = new ws.Server({ port: 5001 });

WebSocketServer.on("connection", (webSocket) => {
    webSocket.on("message", (message) => {
        message = JSON.parse(message);
        console.log(message);
        WebSocketServer.clients.forEach((client) => {
            client.send(JSON.stringify(message));
        });
    });
    webSocket.on("close", () => {
        console.log("close");
    });
});

せつめいしょ

まずはコンストラクタです。
分かりやすいようにコードをちょっと削ってみます。

class AwaitbleWebSocket {
    constructor(url) {
        this.socket = new WebSocket(url);
        this.isOpen = false;
        let _resolve_ = () => { };

        this.socket.addEventListener("open", (event) => {
            this.isOpen = true;
            _resolve_(this);
        });
        return new Promise((resolve, reject) => {
            // 仕事でやると怒られそうな書き方
            this.isOpen ? resolve(this) : _resolve_ = resolve;
        });
    }
}

await(new AwaitbleWebSocket(url))でconstructorがpromiseを返し、接続がopenになるまでボーっとします。
タイミング次第でresolveが電子の海に消えるので、もしかしたらここはintervalを組んだほうがいいかもしれません。どっちでもいいやい

return new Promise((resolve, reject) => {
    // 仕事でやると怒られそうな書き方
    let interval_id = setInterval(() => {
        if (this.isOpen) {
            resolve(this);
            clearInterval(interval_id);
        }
    }, 10);
});

肝心の送信部分。関数な書き方をするには、どの送信がどの返信なのかを引き当てする必要があります。
仕組みは単純で、uuidを付加して送って返信にもそのまま含めてもらうだけです。

class AwaitbleWebSocket {
    constructor(url) {
        this.socket = new WebSocket(url);
        this.messagePool = {};

        this.socket.addEventListener("message", (event) => {
            let data = JSON.parse(event.data);
            if (data.uuid in this.messagePool) {
                this.messagePool[data.uuid](event);
                delete this.messagePool[data.uuid];
            }
        });
    }

    async send(message) {
        let uuid = crypto.randomUUID();
        this.socket.send(
            JSON.stringify(
                Object.assign(
                    { message: message },
                    { uuid: uuid }
                )
            )
        );
        let R = new Promise((resolve, reject) => {
            this.messagePool[uuid] = (_) => {
                resolve(JSON.parse(_.data));
            };
        });
        return R;
    }
}

Social

https://github.com/xoFeulB
https://twitter.com/xoFeulB
https://bsky.app/profile/xofeulb.bluefox.tech

Discussion