0

I have some websocket code in JS. I have a message-handling loop like this:

socket.addEventListener('message', function (event) {
  payload = JSON.parse(event.data)
  method = payload.method
  // Dispatch messages
  if (method == 'cmd1') { 
    handle_cmd1(payload);  // trigger event/semaphore here to wait up waiter
  }
  else if (method == 'cmd2') { ... }
});

And elsewhere, I have a button callback like this:

$('#my-button').change(function() {
  handle_button();
});
async function handle_button() {
  send_msg('msg1', 'hi');
  // wait for server to reply with cmd1
  cmd1_data = await something(); // what?
  alert(`cmd1 data: $(cmd1_data)`);
}

The idea is that the button sends 'msg1' and the server is supposed to reply with 'cmd1' and some info. I want to wait for that reply and then do some more stuff. So my question is how to interlock these? In C++ I'd use a semaphore. I'd rather not spin-loop; is there something in Javascript/JQuery I can use to trigger and then wait for a user-defined event like this? I'm sort of new to JS, and very new to JS async/await.

EDIT: I've made a simple jsfiddle to show what I'm after. http://jsfiddle.net/xpvt214o/484700/

GaryO
  • 5,873
  • 1
  • 36
  • 61
  • I think that You can solve it with a promise – Juan Caicedo Jul 25 '18 at 15:26
  • 1
    Where is `send_msg` defined? You could have that return a promise of the return message (resolving the promise when the socket returns the message). Then you can use `cmd1_data = await send_msg('msg1', 'hi');` – Heretic Monkey Jul 25 '18 at 15:33
  • Sounds like you will need to register a queue of listeners on your socket – Bergi Jul 25 '18 at 15:46
  • @HereticMonkey: right now send_msg is just `socket.send(JSON.stringify({'method': method, 'params': params}));` where socket is a WebSocket. I'd be happy to have it return a promise for the return message, but it's the same issue: what fulfills that promise? How do I notify that promise from my message handler loop? – GaryO Jul 25 '18 at 15:53
  • Implement an EventEmitter for your socket, them you can do something like `await new Promise(res => methods.once("cmd1", res));` – Jonas Wilms Jul 25 '18 at 15:56
  • So, as others have mentioned, you'll likely want to implement something that tracks those messages as they are bouncing around. EventEmitter is a popular pattern for that, as is pub/sub or observer pattern. Essentially, a separate object that sends the messages and listens for the response, allowing other functions to be notified when the response occurs. – Heretic Monkey Jul 25 '18 at 16:31
  • OK, I understand the EventEmitter pattern: I register a handler for the reply message, and when it comes in, that handler gets called. But then what? How does that handler resolve the original promise that handle_button is waiting on? Maybe I just need to read up on promises. It seems to me like they can only be resolved from within the promise function. If I make the promise a global, there doesn't seem to be any `myPromise.resolve()` method on it, for instance. – GaryO Jul 25 '18 at 16:51
  • I added a jsfiddle with a simplified example of what I'm after. Click button1 to start waiting, then click button2 to signal button1's code to continue. But the semaphore part is just comments, because that's what I don't know how to do in js. – GaryO Jul 25 '18 at 17:42
  • Another way to ask it is this: what kinds of things can a Promise function wait for without blocking? I/O, timeout... what else if anything? – GaryO Jul 25 '18 at 18:28

2 Answers2

1

Now that I understand how promises in Javascript work, here's a working example of a promise that can get woken up from anywhere by calling a function:

wakeup = null;

// returns a promise that will be resolved by calling wakeup()
// (could be a list of these or whatever, this is just a simple demo)
function wakeable() {
    return new Promise( (resolve) => {
        wakeup = () => { resolve(true); }
    });
}

// demo of waiting and getting woken up:
async function handle_event() {
    while (true) {
        console.log("waiting...")
        await wakeable(); // returns to event loop here
        console.log("handle event woke up!");
    }
}

handle_event(); // start in "background"
wakeup(); // wake it up
setTimeout(_ => { wakeup(); }, 500); // wake it up again after a delay

What's happening here is that when you call wakeable(), it returns a promise. That promise is constructed with an anonymous function (the one taking resolve as arg); the promise constructor synchronously calls that function, passing it the promise's resolve method. In our case, the function sets wakeup to another anonymous function that calls the original resolve; it's a closure so it has access to that resolve function even when it's called later. Then it returns the new promise.

In this demo, we then await on that promise; that puts the pending promise on a queue, saves the current function state, and returns, like a generator calling yield.

A promise is resolved when its resolve function is called; in this case calling wakeup() calls the promise's internal resolve() method, which triggers any .then methods on the next tick of the Javascript event loop (using the promise queue mentioned above). Here we use await, but .then(...) would work the same way'

So there's no magic; I/O and timeout promises work the same way. They keep a private registry of functions to call when the I/O event or timeout happens, and those functions call the promise's resolve() which triggers the .then() or satisfies the await.

By the way, unlike async in python, leaving a pending promise "open" when the process exits is perfectly fine in Javascript, and in fact this demo does that. It exits when there's no more code to run; the fact that the while loop is still "awaiting" doesn't keep the process running, because it's really just some closures stored in a queue. The event loop is empty, so the process exits (assuming it's in node.js -- in a browser it just goes back to waiting for events).

GaryO
  • 5,873
  • 1
  • 36
  • 61
0

something() should be a method that returns a promise() or should be another method that is also notated with async.

function something(){
  return new Promise(resolve,reject) {
      //... call your database
      // then
      resolve(theResult)
      // or
      reject(theError)
  }
}

The async and await for the most part are really just wrappers around promises. The await returns when the promise calls resolve, and throws an exception when the promise calls reject.

Your async function can return another promise; If it returns another value, it gets turned into a resolved promise with that value.

Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
  • OK, but how do I get the code in the event loop handler (`handle_cmd1`) to resolve that promise? – GaryO Jul 25 '18 at 15:46