4

What is the most straight-forward and direct way to queue a task in the browser event loop, using JavaScript?

Things that don't work:

D. Pardal
  • 6,173
  • 1
  • 17
  • 37
  • Use settimeout with 0. Them minimum is a nonzero minimum – Jaromanda X May 03 '20 at 12:05
  • @JaromandaX [That doesn't work.](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Timeouts_throttled_to_%E2%89%A5_4ms) – D. Pardal May 03 '20 at 12:08
  • What makes you think that it doesn't? Documentation states *if this argument is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle.* – Jaromanda X May 03 '20 at 12:10
  • @JaromandaX 1. The [spec](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-initialisation-steps) says that if `setTimeout` is called more than 5 times, the timeout is set to 4 (ms). "If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4." Try it yourself: `{let x=performance.now(); (function f() { const n=performance.now(); console.log(n-x); x=n; setTimeout(f, 0) })();}`. – D. Pardal May 03 '20 at 12:18
  • All the tools we have like Promises, Mutation Observer, Next tick etc allow you to meddle with microtask. All other tools like requestAnimationFrame, setImmediate, setTimeout etc allow you to meddle with the macrostack (the stack above microtask.) I dont think, there is a way to change the way the main event loop works. However, the way event loop works is that, if microtasks queue more microtasks the loop is considered not finished, so you can go ahead with Promises, I am sure it would be indistinguishable from queing them directly below the main stack. – ibrahim tanyalcin May 03 '20 at 12:19
  • @ibrahimtanyalcin They are not indistinguishable, though. A microtask loop will make the event loop never reach the tasks, pausing rendering, event handling, etc... – D. Pardal May 03 '20 at 12:22
  • @D.Pardal If UI blocking is what you mean, stacking them in the task queue does EXACTLY that. That's why they are indistinguisable. If the loop doesn't end, it is UI blocking, if the microtasks does not end, it is ALSO Ul blocking. – ibrahim tanyalcin May 03 '20 at 12:25
  • I would recommend whatching [this video about the event loop](https://youtu.be/cCOL7MC4Pl0) *before* commenting. – D. Pardal May 03 '20 at 12:27
  • That video is exactly what you don't get. – ibrahim tanyalcin May 03 '20 at 12:29
  • @D.Pardal OK - I did not know that part of the spec – Jaromanda X May 03 '20 at 22:20

1 Answers1

3

MessagePort.postMessage does just that.

onmessage = e => handleMessage;
postMessage("","*");

You can even use a MessageChannel if you want a less intrusive mean:

const channel = new MessageChannel();
channel.port1.onmessage = handleMessage;
channel.port2.postMessage('');

This is currently the only API that does queue a task synchronously, all others implying at least some in parallel execution.

Maybe one day we will have a scheduler.postTask method, which would even allow us to specify some priority for our tasks, but that's for the future only...

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • I was expecting this answer, as [it's what setimmediate.js uses](https://github.com/YuzuJS/setImmediate#postmessage). Let's see if someone knows a better way. – D. Pardal May 03 '20 at 12:23
  • That's the most straigtforward way. Really, it's currently the only API we have which really is just **synchronously** queuing a task. – Kaiido May 03 '20 at 12:26
  • I was just waiting for more answers. They didn't come. It seems that really is the only way then. – D. Pardal May 03 '20 at 12:33
  • Considering transferable objects are buffers,ports and canvases and we pass strings back and forth, is this going to be more suited and performant for ordinary task queuing compared to Promises for instance ? – ibrahim tanyalcin May 03 '20 at 13:34
  • @ibrahimtanyalcinI not sure what you mean... serializing an empty string is nothing. Creating a Promise and forcing the entrance to the microtask endpoint is more work. No need to transfer anything, even though transferring is once again nothing. So.. yes it's better than Promises because Promises don't *queue a task* for one, and because a queuing an empty message is like almost nothing in terms of computations. – Kaiido May 03 '20 at 14:29
  • You need to "pass" the result from the previous computation from port1 to the other message handler of the port2 (even if empty string), even if both ports end up in the same window. This involves 2 extra steps of calling onmessage handlers as opposed to just executing the next task. Do you have a working fiddle and an accompanying jsperf to prove your point ? Because you made the claim – ibrahim tanyalcin May 03 '20 at 16:58
  • 1
    @ibrahimtanyalcin I really don't understand your point, and I fear you actually don't understand the question here... A Promise doesn't call *queue a task*, it doesn't exit the current event loop iteration. If you wish, you can try `const stoopidLoop = () => Promise.resolve().then( stoopidLoop ); stoopidLoop()`. This will block your browser just like a `while(true){}` loop, because it will never let the event loop to actually loop, itwill just make it grow synchronously. So we can't compare both because only *postMessage* does what's being asked, that is to *queue a task*. – Kaiido May 04 '20 at 01:45
  • 1
    @ibrahimtanyalcin in comparison, you can also try `onmessage = e => postMessage('','*'); postMessage("");` it will probably make your CPU spin a bit more, since indeed it will never be able to idle, but still your page will be responsive, all the other events will have their chance to run and more importantly, the browser will be able to *update the rendering*. – Kaiido May 04 '20 at 01:48