#javascript #node.js #express #websocket #socket.io
#javascript #node.js #выражать #websocket #socket.io
Вопрос:
Я хотел бы сохранить некоторую информацию в переменных комнаты сокета, но получаю следующую ошибку: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined
Это мой код:
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
io.sockets.adapter.rooms[socket.roomId].isOpen = true;
io.sockets.adapter.rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
Если я попытаюсь войти socket.roomId
в систему, это вернет что — то вроде rBAhx0
. И когда я регистрируюсь io.sockets.adapter.rooms
, я получаю следующее:
Map {
'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}
Однако, когда я пытаюсь войти io.sockets.adapter.rooms[socket.roomId]
в систему, он возвращается undefined
. Как я могу это исправить?
Ответ №1:
Сокет.io внесла некоторые кардинальные изменения в новейшую версию, и доступ к комнатам стал невозможен, как раньше. Это изменило множество объектов и массивов на карты и наборы, вы можете увидеть это в журналах, которые вы опубликовали.
Set
объекты представляют собой наборы значений. Вы можете перебирать элементы набора в порядке вставки. Значение в наборе может встречаться только один раз; оно уникально в коллекции набора.
Map
Объект содержит пары ключ-значение и запоминает исходный порядок вставки ключей. Любое значение (как объекты, так и примитивные значения) может использоваться либо как ключ, либо как значение.
Доступ к свойствам карты работает иначе, чем доступ к свойствам обычного объекта. Пример:
const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar
То же самое относится и к наборам, однако в вашем случае запрос этого набора, в частности, вероятно, является неправильным подходом, поскольку этот набор содержит только набор идентификаторов, а не фактические объекты room. Даже если бы вы хотели получить значение из набора, вы не смогли бы получить доступ к его свойствам (host, isOpen и players), поскольку это всего лишь строка.
Боюсь, что версия 3.0 сделала прямой доступ к списку всех комнат невозможным. Однако теперь у адаптера есть свойство socketRooms
, которое можно использовать вместо него.
Чтобы облегчить доступ к комнатам сокета, вы должны получить к ним доступ следующим образом:
io.sockets.adapter.socketRooms(socketId);
Однако это все равно просто вернет список строк.
Самым простым решением этой проблемы было бы создать внешнюю переменную за пределами области подключения.
const rooms = {};
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
rooms[socket.roomId].host = socket.username;
rooms[socket.roomId].isOpen = true;
rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
Комментарии:
1. Спасибо за быстрый ответ и разъяснения! Что бы вы порекомендовали мне сделать для хранения переменных? Вместо того, чтобы использовать базу данных, я хотел бы хранить информацию в самой комнате (например, открыта ли игра, кто является хозяином (имя пользователя) и имена пользователей игроков). Есть ли какой-либо другой способ сохранить эти данные, чтобы к ним могли обращаться другие сокеты в той же комнате?
Ответ №2:
Как вы можете видеть в своем собственном журнале, io.sockets.adapter.rooms
является Map
объектом (в более поздних версиях socket.io ), а не простой объект. Это означает, что доступ к комнате осуществляется с .get()
помощью, а не с помощью простого индексирования, такого как то, что вы используете:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
Это просто не сработает, потому что на карте нет простого socket.roomId
свойства.
Если бы вы хотели получить объект room, вам пришлось бы сделать это:
let roomObj = io.sockets.adapter.rooms.get(socket.roomId);
roomObj.host = socket.username;
На мой взгляд, я бы не стал напрямую связываться с объектами комнаты. Мы уже знаем этот сокет.io ранее менял их дизайн, делая любой код, который пытался их использовать, сломанным и нуждающимся в редизайне. Вместо этого я бы, вероятно, просто создал свою собственную структуру данных, в которой я хранил свои собственные данные, и тогда я не буду зависеть от прихотей сокета.ввод-вывод изменяет их дизайн. Прямой доступ к объектам комнаты не является обязательным дизайном или обязательным API для socket.io . Они могут изменить, как это работает, когда захотят, и, действительно, недавно изменили его.
Комментарии:
1. Спасибо за быстрый ответ и разъяснения! Что бы вы порекомендовали мне сделать для хранения переменных? Вместо использования базы данных я хотел бы хранить информацию в самой комнате (например, если игра открыта, кто является хозяином (имя пользователя) и имена пользователей игрока). Есть ли какой-либо другой способ сохранить эти данные, чтобы к ним могли обращаться другие сокеты в той же комнате?
2. @MilanFerus-Comelo — Вы можете просто создать свой собственный объект с
roomId
ключом, который отслеживает ваши собственные данные комнаты, а затем любой сокет может получить доступ к этим данным. Итак, вместо использованияio.sockets.adapter.rooms
используйте свой собственный объект в качестве родительского.
Ответ №3:
После аварийного обновления из сокета.ввод-вывод изменения Array
на Map
. Простым решением было бы изменить:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
Для:
io.sockets.adapter.get(socket.roomId).host = socket.username;