How to let JavaScript wait until certain event happens?
Asked Answered
R

6

18

I am writing a webpage with the following structure:

  1. One section (table A) depends on another section (table B);
  2. Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.

In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.

For now, I am using the following method:

  1. Declare a global variable updated and make it false;
  2. After the first table received input, I make an empty while loop until updated is true;
  3. Add an listener, once the calculation is done and the event received, set updated to true.

This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?

Thanks for any inputs!

Rotterdam answered 1/8, 2011 at 17:51 Comment(1)
If you really need to use polling its way better to wrap your code in a setTimeout which periodically checks the status of updated instead of an empty while loop.Ledge
S
14

Add an listener, once the calculation is done and the event received, set updated to true.

Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.

myEventBus.addListener(function () {
    // do whatever
    updateTable();
    alert('table updated!');
});
Stack answered 1/8, 2011 at 17:57 Comment(0)
S
28

In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:

function getPromiseFromEvent(item, event) {
  return new Promise((resolve) => {
    const listener = () => {
      item.removeEventListener(event, listener);
      resolve();
    }
    item.addEventListener(event, listener);
  })
}

async function waitForButtonClick() {
  const div = document.querySelector("div")
  const button = document.querySelector("button")
  div.innerText = "Waiting for you to press the button"
  await getPromiseFromEvent(button, "click")
  div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Stephenstephenie answered 20/1, 2022 at 15:37 Comment(3)
for jquery : function getPromiseFromEvent(item, event) { return new Promise((resolve) => { const listener = () => { item.off(event, listener); resolve(); } item.on(event, listener); }) }Vasty
Can you explain or give use cases on why it would be benefecial to a promise to use in async/await code?Ledge
@Ledge It's just about coding preferences. There is nothing you can do with the async-await model, that you could not do with event callbacks, however I personally find that in many cases the async-await code is easier to read later. Note that it's especially useful for one-time events (as in: wait until the button is pressed (once)). If you want something like "every time the button is pressed, do X", I would probably prefer callbacks without the promise/async/await stuff.Stephenstephenie
S
14

Add an listener, once the calculation is done and the event received, set updated to true.

Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.

myEventBus.addListener(function () {
    // do whatever
    updateTable();
    alert('table updated!');
});
Stack answered 1/8, 2011 at 17:57 Comment(0)
D
8

Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.

What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.

//this is just illustrative
//Your actual code will be probably very different from this.

function update_part(){
    //do something
    signal_finished_part()
}

var parts_done = 0;
function signal_finished_part(){
    parts_done ++;
    if(parts_done >= 5){
        signal_all_parts_done();
    }
}

function signal_all_parts_done()
{
    //do something to table A
}
Dave answered 1/8, 2011 at 19:29 Comment(0)
G
1

You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.

Glucoside answered 7/9, 2016 at 4:31 Comment(0)
H
0

I had to refactor a JS app that was full of setTimeout() to handle event synchronization (OMG)...

So I've spend some time to create a tiny event kernel as a starting point.

You can define listener waiting for other listeners in a specific event scope.

So instead of calling functions you fire events.

Each tool/component can react accordingly in the correct order (because you explicitly say that module B needs A). The event object is passed along the chain and can hold a context (aka datas you need to share).

OC under the hood it uses Promise.

here is some dummy code

qk.addEventListener('my_event', (e) => {
    // something
    notNeeded();
});

// your module foo does some init stuff
qk.addEventListener('my_event', async (e) => {
    // something async
    e.context.needed = await needed();
}, 'foo');

// somewhere else in your app your module bar is waiting after foo to set a specific context
qk.addEventListener('my_event', (e) => {
    // something after the async callback
    needing(e.context.needed);
}, 'bar', 'foo');

// call everything
qk.dispatchEvent(new QKE('my_event')).then(() => {
    // event my_event fully dispatched
    happyEnd();
});
// or await qk.dispatchEvent(new QKE('my_event'));

more on here

Homans answered 24/8, 2023 at 14:33 Comment(0)
S
0

A one-liner which awaits an event on an element:

await new Promise(r => el.addEventListener("click", r, {once:true}));
// `el` was clicked, now do stuff

Or if you want to wait for any of several events to occur:

await new Promise(r => ["mouseenter", "mousemove"].forEach(n => el.addEventListener(n, r, {once:true})));
// at least one of the events happened on `el`, now do stuff

Just change the event name(s) to suit your use case, and change el to refer to your element.

Samuelsamuela answered 9/7, 2024 at 23:48 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.