user1584421
user1584421

Reputation: 3843

What is the proper way to emit an event with socket.io?

I want to emit an event to the client when a long fucntion comes to an end. This will show a hidden div with a link - on the client side.

This is the approach i tested:

//server.js

var express    = require('express');
var app        = express();
var http       = require('http').Server(app);
var io         = require('socket.io')(http);

require('./app/routes.js')(app, io);

//routes.js

app.post('/pst', function(req, res) {
        var url = req.body.convo;

        res.render('processing.ejs');

                myAsyncFunction(url).then(result => {
                    console.log('Async Function completed'); 

                    socket.emit('dlReady', { description: 'Your file is ready!'});

                    //do some other stuff here

        }).catch(err => {
                    console.log(err);
                    res.render('error.ejs');
         })
});

I get this

ERROR: ReferenceError: socket is not defined

If i change the socket.emit() line to this:

io.on('connection', function(socket) {
    socket.emit('dlReady', { description: 'Your file is ready!'});
});

Then i don't receive an error, but nothing happens at the client.

This is the client code:

<script>
      document.querySelector('.container2').style.display = "none";
      var socket = io();
      socket.on('dlReady', function(data) { //When you receive dlReady event from socket.io, show the link part
        document.querySelector('.container1').style.display = "none";
        document.querySelector('.container2').style.display = "block";
      });
</script>

Upvotes: 0

Views: 895

Answers (1)

jfriend00
jfriend00

Reputation: 707328

This whole concept is likely a bit flawed. Let me state some facts about this environment that you must fully understand before you can follow what needs to happen:

  1. When the browser does a POST, there's an existing page in the browser that issues the post.
  2. If that POST is issued from a form post (not a post from Javascript in the page), then when you send back the response with res.render(), the browser will close down the previous page and render the new page.
  3. Any socket.io connection from the previous page will be closed. If the new page from the res.render() has Javascript in it, when that Javascript runs, it may or may not create a new socket.io connection to your server. In any case, that won't happen until some time AFTER the res.render() is called as the browser has to receive the new page, parse it, then run the Javascript in it which has to then connect socket.io to your server again.
  4. Remember that servers handle lots of clients. They are a one-to-many environment. So, you could easily have hundreds or thousands of clients that all have a socket.io connection to your server. So, your server can never assume there is ONE socket.io connection and sending to that one connection will go to a particular page. The server must keep track of N socket.io connections.
  5. If the server ever wants to emit to a particular page, it has to create a means of figuring out which exact socket.io connect belongs to the page that it is trying to emit to, get that particular socket and call socket.emit() only on that particular socket. The server can never do this by creating some server-wide variable named socket and using that. A multi-user server can never do that.
  6. The usual way to "track" a given client as it returns time after time to a server is by setting a unique cookie when the client first connects to your server. From then on, every connection from that client to your server (until the cookie expires or is somehow deleted by the browser) whether the client is connection for an http request or is making a socket.io connection (which also starts with an http request) will present the cookie and you can then tell which client it is from that cookie.

So, my understanding of your problem is that you'd like to get a form POST from the client, return back to the client a rendered processing.ejs and then sometime later, you'd like to communicate with that rendered page in the client via socket.io. To do that, the following steps must occur.

  1. Whenever the client makes the POST to your server, you must make sure there is a unique cookie sent back to that client. If the cookie already exists, you can leave it. If it does not exist, you must create a new one. This can be done manually, or you can use express-session to do it for you. I'd suggest using express-session because it will make the following steps easier and I will outline steps assuming you are using express-session.
  2. Your processing.ejs page must have Javascript in it that makes a socket.io connection to your server and registers a message listener for your "dlready" message that your server will emit.
  3. You will need a top-level io.on('connection', ...) on your server that puts the socket into the session object. Because the client can connect from multiple tabs, if you don't want that to cause trouble, you probably have to maintain an array of sockets in the session object.
  4. You will need a socket.on('disconnect', ...) handler on your server that can remove a socket from the session object it's been stored in when it disconnects.
  5. In your app.post() handler, when you are ready to send the dlready message, you will have to find the appropriate socket for that browser in the session object for that page and emit to that socket(s). If there are none because the page you rendered has not yet connected, you will have to wait for it to connect (this is tricky to do efficiently).

If the POST request comes in from Javascript in the page rather than from a form post, then things are slightly simpler because the browser won't close the current page and start a new page and thus the current socket.io connection will stay connected. You could still completely change the page visuals using client-side Javascript if you wanted. I would recommend this option.

Upvotes: 1

Related Questions