0

Trying to figure out a sure fire way to confirm all socket methods complete successfully.

In this case I am trying to add an array of rooms (socket.join(roomname)); to a socket and then confirm that they have all been added but I cannot seem to figure out a way to fire console.log(socket.rooms); after it has been set. It always prints the default room and I only see the other rooms after I redundantly emit from the client again.

I am really trying to avoid the usage of a setTimeout or a check with a setInterval and just trying to see how I can make a promise to ensure the socket is joined with each room. Currently the console log only logs the entire socket itself. Anyone have any experience with this?

io.on("connection", socket => {
    console.log("New client connected");
    socket.on('fetchConvos', async function(rooms) {
        let roomsString;
        let result = await mapper(rooms); // makes into array
        let joinRooms = new Promise((resolve, reject) => { // Promise to add client to all rooms
            for (let i = 0; i < result.length; i++) {
                resolve(socket.join(result[i]));
            }
            throw new Error();
        })
        // Complete putting user in an array of rooms and then print confirmation that rooms were added
        joinRooms.then(() => {
            console.log(socket.rooms);
            result.forEach((string, index) => {
                if (string) {
                    roomsString += string + ", ";
                }
            });
            socket.emit("chat", "You are now in these rooms: " + roomsString); // emit back to socket 
        }).catch(error => console.log(error));  
   })
   socket.on("disconnect", () => {
        console.log("Client disconnected");
   });    
});
lovgrandma
  • 133
  • 1
  • 9
  • Why would you want to resolve in a for loop? Think about it. A Promise will transition from "pending" to "fulfilled" or "rejected" ONCE. Calling `resolve` or `reject` a second time will have zero effect. – Roamer-1888 Mar 07 '20 at 11:32
  • What does `socket.join()` return? Is it a Promise? – Roamer-1888 Mar 07 '20 at 11:34
  • This is correct, I am thinking that I could put a new promise in the for loop and then set a variable to resolve once all of them have completed. But yes, I dont think socket.join() returns a promise, it is returning the socket obj it seems it starts with Socket { .. The rooms key is in here like rooms: { "cTbjCBD4GpI-p0_nAAAA": "cTbjCBD4GpI-p0_nAAAA" } but thats not the room I added. Thats the default socket room for the fundamental functionality. When I make another emit to fetch the convos it returns them but I was just trying to do everything with one emit and zero setTimouts just promises – lovgrandma Mar 07 '20 at 23:25

1 Answers1

0

OK, reading here in the Socket.IO documentation, is the statement ...

To leave a channel you call leave in the same fashion as join. Both methods are asynchronous and accept a callback argument.

... which is highly significant.

Better explained here.

So, "joins" can promisified be and the rusulting Promises aggregated with Promise.all().

io.on('connection', socket => {
    console.log('New client connected');
    socket.on('fetchConvos', async function(rooms) {
        try {
            let results = await mapper(rooms); // array
            // first, map results to array of Promises, where each Promise represents a socket.join() operation.
            let promises = results.map(room => {
                // here, promisify socket.join() on-the-fly and return Promise
                return new Promise((resolve, reject) => {
                    socket.join(room, (err) => {
                        if(err) reject(err);
                        else resolve(room);
                    });
                });
            });

            // Here, you probably want any failures of socket.join() not to scupper promise aggregation with Promise.all();
            // see https://stackoverflow.com/a/31424853/3478010
            const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

            let rooms = (await Promise.all(promises.map(reflect))).filter(o => o.status !== 'rejected').map(o => o.v);
            let rooms_ = Object.keys(socket.rooms); // from documentation

            // rooms and rooms_ should be similar, maybe the same ???
            console.log(rooms);
            console.log(rooms_);

            // now, map rooms, or rooms_, to whatever you wish to emit to `chat`, eg ...
            let roomsString = rooms.join(', '); // .join() here is the Array method Array.prototype.join()
            socket.emit('chat', `You are now in these rooms: ${roomsString}`); // emit to `chat`

            // Based on example in the documentation, you can also broadcast to to everyone in each room joined, eg ...
            rooms_.forEach(room => {
                io.to(room).emit('a new user has joined the room'); // broadcast
            });
        }
        catch(error) {
            console.log(error);
            socket.emit('chat', error.message); // for debugging
        }
    });
    socket.on("disconnect", () => {
        console.log("Client disconnected");
    });    
});

Alternatively, socket.join() also accepts an array of rooms, which makes things simpler:

io.on('connection', socket => {
    console.log('New client connected');
    socket.on('fetchConvos', async function(rooms) {
        try {
            let results = await mapper(rooms); // array
            // promisifying socket.join() allows `await` to be used (and avoids nesting).
            await new Promise((resolve, reject) => {
                socket.join(rooms, (err) => {
                    if(err) reject(err);
                    else resolve();
                });
            });

            let rooms_ = Object.keys(socket.rooms); // from documentation

            // rooms and rooms_ should be similar, maybe the same ???
            console.log(rooms);
            console.log(rooms_);

            // now, from rooms, or rooms_, compose whatever you wish to emit to `chat`, eg ...
            let roomsString = rooms.join(', '); // .join() here is the Array method Array.prototype.join()
            socket.emit('chat', `You are now in these rooms: ${roomsString}`); // emit to `chat`

            // Based on example in the documentation, you can also broadcast to to everyone in each room joined.
            rooms.forEach(room => {
                io.to(room).emit('a new user has joined the room'); // broadcast
            });
        }
        catch(error) {
            console.log(error);
            socket.emit('chat', error.message); // for debugging
        }
    });
    socket.on("disconnect", () => {
        console.log("Client disconnected");
    });    
});

None of this is tested and there's a some guesswork involved. Be prepared to do some debugging.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • 1
    Thankyou for showing me both of these methods. Both ways make sense. This really clears up my understanding of promises and will be helpful for the rest of development. I implemented the first one in my own way and it works as expected. – lovgrandma Mar 09 '20 at 14:39
  • No worries. Good luck with it. – Roamer-1888 Mar 09 '20 at 22:44