Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Does async/await execution have a higher priority than setTimeout callbacks?

I was writing some code where a user would click a button and the time of the next locally-visible solar eclipse would be computed.

This can be a very time-consuming calculation. So I wrote the calculation code with a couple of nested async functions, breaking all of the computations into bite-size pieces, allowing execution to yield frequently so that the user’s web browser wouldn’t lock up during the calculation.

Here’s a grossly simplified test case of the timing issues involved, without the astronomical complexity (available on CodePen):

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 15000;

async function workWorkWork(msg) {
  return new Promise(async (resolve) => {
    const start = performance.now();

    while (performance.now() < start + 5000) {
      await new Promise((innerResolve) => {
        let t = 0;

        for (let i = 0; i < 1e7; ++i) t += Math.sin(Math.random());

        console.log(msg || t);
        // innerResolve(); // What I thought should have been sufficient
        setTimeout(innerResolve); // What I had to do instead
      });
    }

    resolve();
  });
}

let busy = false;

function doStart() {
  if (busy)
    return;

  const button = document.getElementById('my-button');

  button.innerText = 'Started';
  button.style.background = 'red';
  busy = true;

  (async () => {
    setTimeout(() => (button.innerText = 'Busy'), 1000);
    workWorkWork('other work');
    await workWorkWork();
    busy = false;
    button.innerText = 'Start';
    button.style.background = 'unset';
  })();
}

After the user clicked the button, I wanted to wait for one second to go by before showing a busy indicator. The busy indicator was activated using a setTimeout called just before await-ing the calculation.

To my surprise, my the UI responded almost as if the async/await was blocking code. The whole calculation finished after several seconds, and then the busy indicator appeared, way too late.

Console output demonstrates that if I start up two calls of workWorkWork simultaneously, their output is interleaved in the web console, so each execution instance is properly yielding time to the other.

But the async function code doesn’t yield any time to the setTimeout that was queued up before the function was called. It doesn’t even let the DOM update with changes made before the async call and before the setTimeout.

The only way to get the setTimeout callback to run in a timely manner appears to be using other setTimeout calls within my async functions.

My first encounter with this problem was inside Angular code, and I figured I might be seeing an Angular-specific problem with the Angular Zone.js craziness, but I’ve now replicated the same problem in plain-ol’ JavaScript.

Is this the behavior I should have expected? Is this documented anywhere?

Googling various combinations an permutations of async, await, setTimeout, Promise, etc., yields a lot of material which unfortunately has nothing to do with what I’ve run into here.

Correct behavior after clicking Start:

Button turns red, text changes to "Started".
About one second later, text changes to "Busy"
About four more seconds later, red background disappears and text changes back to "Start".

Bad behavior after clicking Start, without extra setTimeout:

Button makes no immediate change of text or color
About five seconds later, text changes to "Busy", and gets stuck that way.

>Solution :

Fulfilled promises are served via a microtask queue which gets handled BEFORE timer events. await works off promises so it also goes before timer events.

So, if both a timer event and a promise fulfillment are both waiting to run their handlers, then the promise will get served first.

Here’s some more info on the microtask queue or more specifically the PromiseJobs queue.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading