1

I'm using a JS library to stream server-sent-events on my html page:

    <html>
    <textarea rows='14' id="value" placeholder="anything you want here"></textarea>
    <button type="button" onclick="post(clip)">get</button>
    
    </html>
    <script type="text/javascript" src="sse.js"></script>
    <script>

url = "http://ac6ba97b046a5dcc677e.elb.us-east-1.amazonaws.com/myapi";
let textArea = document.getElementById("value");

function clip(){
  s = textArea.value;
  s = s.slice(0, -5);
  textArea.value = s;
  console.log('hello');
}

function post(callback){
    var v = String(textArea.value);
    console.log(v);
    var source = new SSE(url, {
      method: 'POST',
      headers: { 'Content-Type': 'text/plain' },
      payload: v
    });
    var arr = [];
    source.addEventListener('message', function (e) {
      arr.push(e.data);
      textArea.value = arr.map(el => el || " ").join('');
    });
    source.stream();
    callback();
}

</script>

When the button is clicked, data is sent to a server using POST method and the textbox is populated with data received from the server. I would like to clip the text in the textbox with clip() after the post() function is executed. Execution process must be like this:

1. post() logs textArea value
2. source.stream() is executed, textbox populated
3. clip() clips last 5 characters and logs 'hello'

But I instead get this:

1. post() logs textArea value
2. clip() clips last 5 characters and logs 'hello'
3. source.stream() is executed, textbox populated

For some reason clip() is being executed before source.stream() even after adding a callback.

The sse.js file that I'm using.

[EDIT] After moving callback() to the end of 'message' handler, the issue still persists:

function post(callback){
    var v = String(textArea.value);
    console.log(v);
    var source = new SSE(url, {
      method: 'POST',
      headers: { 'Content-Type': 'text/plain' },
      payload: v
    });
    var arr = [];
    source.addEventListener('message', function (e) {
      arr.push(e.data);
      textArea.value = arr.map(el => el || " ").join('');
      callback();
    });
    source.stream();
}

Does anyone know what might be causing this?

Bob Ross
  • 53
  • 6

1 Answers1

0

When your script calls source.stream();, it is doing an XMLHttpRequest.send() operation, which is async by default.

So, what is happening:

  1. user clicks, and post() is called
  2. SSE object and its event listener is set up
  3. source.stream() is called, which sends the request to the server.
  4. callback() (i.e. clip()) is called
  5. Server sends back a response
  6. Your message event handler is called
  7. textArea.value is set

Luckily the fix is simple: you only want callback() to be called when a message is received. So move callback() to be at the end of the message event handler, not at the end of post().

It will do this after every message event that is received. If you only wanted it to happen after the first event, you will need to implement some logic to keep track of how many events have been received. (And if there will only be one message event, you should be using an Ajax call, not an SSE/EventSource call.)

UPDATE: Discussion in the comments are starting to get out of scope of your original question (where the answer is, simply put, "it is async, not sync"). But I think it is worth pointing out here that you set up a new SSE object every time the user clicks the button. Each SSE object will have its own dedicated TCP/IP socket and its own listener function. This is (generally) not a good idea: instead create the SSE connection once at the start of your web app.

And, though your SSE polyfill library allows using POST, the standard SSE does not. If you only want the app to poll the server when the user presses the button, consider switching from using SSE to normal AJAX.

Darren Cook
  • 27,837
  • 13
  • 117
  • 217
  • I tried that but the issue seems to persist. Textbox value is being clipped before the stream begins. (I edited the question, to show that I changed callback()'s place) – Bob Ross Jan 25 '21 at 16:20
  • Try changing `callback()` to `setTimeout(callback,0)`. That just makes it get called after the UI has had chance to update. Alternatively, change `clip()` to take `s` as an argument, rather than get it from `textArea.value`: that will guarantee it uses the text you want it to use, won't it. – Darren Cook Jan 25 '21 at 17:12
  • I'll try that. By the way when I call post() (or source.stream() to be more exact) textbox takes around 10-15 seconds to completely update. Will the variation in textbox's update time be a problem? – Bob Ross Jan 25 '21 at 19:43
  • I tried adding `console.log('hello')` at the beginning and setting `setTimeout(callback,0)` at the end of `message` EventListener and got an interesting result: 127 'hello' messages in the console and a textbox that got dynamically clipped 127 times, so that the full output couldn't be displayed. Is it possible to store the `textbox.value` variable after the `message EventListener` has done it's work, and then perform `clip()`? – Bob Ross Jan 26 '21 at 09:02
  • This is getting a bit out of scope for your original question, but I did just do an update to my answer. Take a look at online/book examples of using SSE to make a chat application, if that is what you are building. And, then if you still have questions, consider starting a new question. – Darren Cook Jan 26 '21 at 09:29