Источник: Nuances of Programming
Уже не единожды на просторах интернетов обсуждались плюсы вебсокетов над xmlhttprequest(ajax) запросами. Из них основные — это скорость доставки(т.к. соединение всегда открыто) и экономия ресурсов (т.к. соединение открыто единожды). Особенно хорошо вебсокеты показывают себя при больших нагрузках, где производительность начинает отличаться не в разы, а на порядок.
НО! Как по мне, есть один большой минус — это потеря состояние вызова. О чем это я? Ajax уже везде обернуто в Promise (будь то браузерный fetch, axios или superagent) и мы имеем удобный интерфейс, пользуясь ajax. Вот пример:
Так на каждый вызов fetch мы описываем удачную логику в then, а неудачную в catch. И сколько бы вызовов не произошло, мы точно знаем на какой запрос получили ответ. Удобненько? Да! С вебсокетами все сложнее:
Если вы уже игрались в вебсокеты, то знаете, что при получении пакета вебсокет возвращает нативное событие в котором в поле data будет лежать строка(и только строка), в которой может быть как и успешный ответ, так и ошибка. И вообще на кой нам строка…
А если мы отправим 20 запросов в очень короткий промежуток, а сервер каждый запрос обрабатывает с разной скоростью (и вернет ответы по мере готовности), то получится, что пакеты-ответы мы получим обратно в случайном порядке (так кстати обычно и происходит), как определить, на какой запрос мы получили ответ? Получается, что у нас есть крутая, быстрая сабля, махать которой мы не умеем. Жизнь начинает казаться серой, давайте исправлять!
На просторах Github я не нашел достойной реализации, скорее потому что универсальной её сделать сложно, но я уверен, что мы можем описать кейс под конкретно наш случай и уложиться в пару десятков строк. Готовьте спицы и резину, пишем велосипед!
Итак, давайте определимся с концепцией:
- Во-первых, для любого общения нужен протокол (именно поэтому люди здороваются, прежде чем начать общение, устанавливают язык/протокол/формат общения). Давайте его придумаем! Условимся, что клиент всегда будет называть метод который хочет вызвать, а сервер выдавать результат вызова этого метода, либо ошибку (где будут поля message и code ошибки), если такового не существует.
- Мы хотим какую-то простую функцию, которую будем вызывать, например так: WS(“название_метода”, “параметры”).then().catch()
- Чтобы на конкретный ответ мы знали, какой запрос ему соответствовал, нам нужен маркер (uniq_id), это будет любой уникальный айди. Я составлю его из случайно сгенерированной строки и текущего таймпстемпа. И когда сервер пришлет помеченный нашим id ответ, который мы можем ассоциировать с тем, что лежит на фронтенде, но нам нужен какой-то message-broker для правильного ассоциирования айди.
- Чтобы хранить и управлять id, можно было обойтись массивом, либо списком и парой функциями поверх, но мы возьмем вот эту полезную штуку. Классический паттерн Event Emitter, который дает возможность подписываться/отписываться на события и вызывать callback по событию. Названием события будет уникальный id.
- В качестве сервера возьмем node.js ws и express.js . Потому что быстро и просто. Но вы можете попробовать другой бекенд.
Начнем с сервера:
Во-первых, создадим json заглушки, которые просто будут возвращать какие-то данные, без разницы. Это нужно будет для тестов. Вы их найдете в ./app/stub/
Далее основа сервера, index.js
Методы-хелперы вынесены в отдельный файл app/utils/index.js
Теперь клиентская часть!
CSS и index.html не особо интересно, там 4 кнопки которые вызывают соответствующие функции и записывают результат в соседний блок. Все находится в папке public
Эффект достигнут!
Весь исходный код можете увидеть тут.
Возможно кто-то заметил некое идейное сходство нашего примитивного “протокола” с JSON-RPC, в следующей статье я расскажу, как подружить вебсокеты с JSON-RPC и решить последний недостаток, в данной реализации нету “гарантии” доставки пакета.
Читайте нас в телеграмме и vk