skip to Main Content

So, basically, I want to do is:

  1. Get an Event from an event listener,
  2. handling that event,
  3. waiting for it to finish,
  4. and then pass to another in the queue,

so if I get like 15 an event 15 times, I don’t want to be handled all at the same time but more like event handle-await seconds event. I’ve tried like this to have one printed, then 5 seconds, then two with multiple clicks but what I’ve got was a lot of "one" at the same time and then a cascade of "two."

document.addEventListener("click", async function(event) {
    if (!event.target.matches(".button-name"));
    console.log("one");
    await sleep(5000);
    console.log("two");
    event.preventDefault();
}, );

async function sleep(milliseconds) {
    return new Promise((resolve) =  > setTimeout(resolve, milliseconds));
}

3

Answers


  1. To fix this problem, you need to use an async generator function that yields each click event as it occurs.

    For example:

    function sleep(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function* getClickEvents() {
      let resolve;
      let promise = new Promise((r) => {
        resolve = r;
      });
      document.addEventListener("click", (event) => {
        resolve(event);
      });
      while (true) {
        const value = await promise;
        yield value;
        promise = new Promise((r) => {
          resolve = r;
        });
      }
    }
    
    async function handleClickEvents() {
      for await (const event of getClickEvents()) {
        if (event.target.matches(".button-name")) {
          console.log("one");
          await sleep(5000);
          console.log("two");
          event.preventDefault();
        }
      }
    }
    
    handleClickEvents();
    

    You can learn more about how JavaScript handles events and callbacks using the event loop and the queue from these sources:

    Login or Signup to reply.
  2. A fairly simple solution would be to have a variable hold a promise. We can then use this promise as a queue. Whenever the event handler is called, we can chain a new task to this queue.

    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    
    let queue = Promise.resolve();
    document.addEventListener("click", function (event) {
      queue = queue.then(async function () {
        console.log("one");
        await sleep(5000);
        console.log("two");
      });
    });

    There are a few important things to note here.

    1. The above doesn’t handle exceptions. Whenever one of the queued tasks fails the entire queue will stop. To prevent this make sure each added task has its own catch mechanism.

      queue = queue.then(...).catch(/* handle exception */);
      
    2. event.preventDefault() can only be called as direct response to an event. If you call it in an queued function, the window of opportunity is gone so it will not have any meaningful effect.


    In the snippet above queue is visible to other code within the same scope. If you do not want this you can wrap the above handler in an IIFE.

    document.addEventListener("click", (function () {
      let queue = Promise.resolve();
      return function (event) {
        queue = queue.then(...);
      };
    })());
    
    Login or Signup to reply.
  3. Why it doesn’t work.

    When you execute an await operator, the execution context is copied and saved in heap memory, and execution of the next lower JavaScript execution context on the call stack is resumed, or if there is none, control returns to the event loop.

    If await is used in an event handler, the handler is called via the event loop and control returns to the event loop immediately when an await operator is executed. This allows more events to occur, which is why you get multiple "ones" from events as they occur, and then a lot of "twos" as the sleep promises become settled after a delay.

    You could put data from Event objects into a queue, or possibly event objects themselves1, and process them asynchronously after being decoupled from the event processing model. Some design and experimentation would be called for if events have default actions.

    Depending on the aim of the code there may be alternatives to the queue as well: if throttling events is the goal, meaning to ignore new events if a previous one of the same kind is being processed, then some state based processing as opposed to a queue could be used. As always, a final design would depend on real application requirements.


    1 Never use window.event in event handlers. It comes from the design of Internet Explorer and Microsoft’s JScript architecture. It can be overwritten if events occur while an event handler is awaiting something during asynchronous code execution.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search