I'm reading a bit about micro tasks and such, because at the moment I have a project that is poorly optimized and some tasks make the UI hang.

I've solved 95% of that by using a (Service) Worker for the heaviest tasks. But there's still some code that just has to be on the main thread and I'm wondering what the best way is to optimize that code.

I basically have 2 wishes:

  1. I want a function to wait a little bit before executing, just enough to let the browser do any necessary UI drawings / changes.
  2. But after that, I do want that function to execute as soon as possible. If I can prevent it, I don't want the task to be put at the very end of the browser's task queue. The reason for this is because the function changes the value of a variable and other functions down the line would benefit from having the latest update of the value of that variable.

After reading about micro tasks, I'm not sure if they're the right tool for the job. Because as far as I understand, the browser's decision on when to run a microtask has not so much to do with the UI, but more with what other macro tasks are on the task queue.

These are the alternatives that I've been able to find:

  • setTimeout() with a timeout of 0, I've read that the browser will automatically increase the time-out if it needs to.
  • requestAnimationFrame(), but that seems to always wait at least 1/60th of a second. If the UI is done sooner than that, then I'd want my function run sooner than that.
  • requestIdleCallback() sounds perfect, except that it's not supported by all browsers.
  • using new MessageChannel() to send an empty message from one port to the other; then executing the task when the 2nd port receives the message. The only reason I'm considering this one is because apparently Facebook uses it in React to queue a task in the browser. On Node React uses setImmediate(), but since that doesn't exist in the browser, apparently Facebook's developers thought this was the best alternative. And because I'm assuming they're cleverer than me, I think there must be something to it, right?
1

There are 1 answers

0
encoder On

If web workers are an option (see comlink or do vanilla implementation) otherwise see the rest of my answer.

Considering your wish #1, I understand that you want consistent frame rate i.e. use only ideal time and next critical UI task should supersede your task.

All the options that you have listed out will not work out if the task runs longer than time for next Paint. Once the task is running it will have to end before browser can do anything else. So the problem is not with scheduling per say but preempting the task for more critical UI tasks.

In terms of scheduling both setTimeout and requestIdleCallback will not satisfy your wish #2 as, setTimeout will push new task at the end of the queue and requestIdleCallback (being worse of two in terms of priority) will wait for all the tasks to complete.

There is no straight forward way to automatically preempt a task in javascript as of now (to best of my knowledge). Even libraries like React can cause UI throttling if pushed hard enough.

Solutions that we have at hand.

  1. Recursive SetTimeout.
  2. Generator functions with some form of scheduling.

In both scenario you will be responsible for breaking down the task in smaller chunks that can "hopefully" complete before next UI paint.

Both work in same fashion pretty much with only difference being that you will need to chain tasks in setTimeout and in generator you can yield. The generator approach is way better in my opinion in terms of syntax as you will just need to add yield statement at different places. Not ideal but better.

Generator Example.

function* task = () => {
  let i = 0;
  while (true) {
    i = i+1 % 10;
    yield;
  }
}

const callback = () => {
  task.next();
  setTimeout(callback, 0);
}

setTimeout(callback, 0);

P.S. Personally I wish there was a simple solution, I've looked for one for a long time. Let me know if this answers your question, if you have any doubts or you find anything interesting around this topic.