2

I was recently watching a workshop video by Will Sentence on async javascript and I am having trouble understanding whether microtask queue always has priority over macrotask queue.

function display(data) {console.log(data)}
function printHello() {console.log(“Hello”);}
function blockFor300ms() {/* blocks js thread for 300ms with long for loop */}

setTimeout(printHello, 0);

const futureData = fetch('https://twitter.com/will/tweets/1')
futureData.then(display)

blockFor300ms()

console.log(“Me first!”);

In this example, Will explains that printhello is added to the macrotask (callback) queue then the fetch request gets executed and suppose at 201ms of the code exuction, the display callback is added to the microtask queue. Meanwhile the blockFor300ms had blocked the code for 300ms. At about 301ms Me first! gets console logged, then since the microtask queue has priority over the macrotask queue, the data from the fetch request gets logged and finally the setTimeout's Hello. So the order of the console.logs:

1. console.log("Me first!")
2. console.log(data) // data from fetch request
3. console.log("Hello")

I tried executing similar code example in various environments (Chrome, Firefox, Safari, node), with various blocker functions (with varying durations) and I always get:

1. synchronous code console.log("Me first!")
2. console.log from the setTimeout
3. console.log with data from fetch request .then

I thought maybe the result depends on when the fetch request gets resolved and the data is received but I have also tried blocking the code for more than 10 or 20 seconds and the outcome is always setTimeout first then the fetch. Whats going on here and what's the reason behind this?

Here is the code I tested with:

function display(data) {console.log("Fetched data:", data);}
function printHello() {console.log("Hello")}
function blockForDuration(duration) {
  const startTime = Date.now();
  while (Date.now() - startTime < duration) {}
}

setTimeout(printHello, 0);

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => response.json())
  .then(display)
  .catch(error => console.error("Fetch error:", error));


blockForDuration(10000);

console.log("Me first!");

/*
  Console log order
  1. "Me first!"
  2. "Hello"
  3. "Fetched data:", data
*/

3
  • 1
    Your fetch() Promise chain does not call display() until after the second Promise has resolved (the one for response.json()). Commented Feb 6, 2024 at 3:00
  • no that's not the problem, hang on Commented Feb 6, 2024 at 3:15
  • Also highly related but with no upvoted answers: stackoverflow.com/a/76553214/3702797 Commented Feb 6, 2024 at 4:59

1 Answer 1

-1

I think this is clearer if you write the code with async and await, because it's easier to get "in between" the things that happen:

!async function() {
        function display(data) {console.log("Fetched data:", data);}
        function printHello() {console.log("Hello")}
        function blockForDuration(duration) {
          const startTime = Date.now();
          while (Date.now() - startTime < duration) {}
        }

        setTimeout(printHello, 0);

        const p = fetch('http://jsonplaceholder.typicode.com/posts/1');
        console.log("back from fetch with promise");
        const pr = await p;
        console.log("fetch initial promise resolved");
        const j = await pr.json();
        console.log("have JSON");
        display(j);

        blockForDuration(10000);

        console.log("Me first!");
}();

In this version, there's a log message after fetch() returns but before the promise is awaited. Note that the fetch() request has to take some time, but the Promise comes back pretty much immediately, so that's the first thing logged.

The next thing logged is "Hello" from the setTimeout(), which runs in a macrotask that happens after the fetch() starts doing its real work and the function is suspended. That's what's happening in the first call to .then() in the original. So the .then() callback in the original runs in a microtask, but its in a microtask after a different macrotask; it's basically when the HTTP request completes. That's an asynchronous process, and for that matter so is the .json() call. The Promise resolutions are on their own macrotask (when .then() actually runs and queues up the callback); it's the callback that runs in the microtask.

Note that the await version logs the "Me first" message last, because of the nature of await. That code is effectively in a .then() that's synthesized after other stuff. You could probably rearrange the await version so that it's more like the original.

edit — here's a version much closer to the original:

function display(data) {console.log("Fetched data:", data);}
function printHello() {console.log("Hello")}
function blockForDuration(duration) {
  const startTime = Date.now();
  while (Date.now() - startTime < duration) {}
}

setTimeout(printHello, 0);

const p1 = fetch('https://jsonplaceholder.typicode.com/posts/1');
console.log("got first fetch promise, now calling .then()");
const p2 = p1.then(response => (console.log("first promise resolved"), response.json())).then(data => (console.log("JSON resolved"), display(data)));

blockForDuration(10000);

console.log("Me first!");

This stuff is interesting and really good to know, in a "back of your head" kind of way, but my personal advice would be to shy away from making significant architectural decisions based on the little details of how the task mechanisms interact. I won't say there aren't situations where it might be very important, but in most day-to-day web programming it's little more than an interesting detail.

Sign up to request clarification or add additional context in comments.

7 Comments

What is "clean up the promise" meant to do?
I don't think this explanation is very useful to the OP, who specifically wants blockForDuration(10000) to run while the fetch() promise is resolved by the network task, to be scheduled potentially before the timer task.
@Bergi (1) oh sry that's a fragment; I was getting weird timeout errors in Node for a little bit, it doesn't seem to actually do anything. (2) that's what happens in the 2nd version of the code tho, "Me First" comes before the promise resolution (the first promise)
@Bergi also I'm using Firefox, I haven't tried in Chrome but the Chrome console in my experience is a little "more weird" because it introduces asynchrony.
Maybe? If the promises were to immediately fulfill, it shouldn't matter, their handlers would all get scheduled in the same microtask checkpoint, before the timer task. I mean, you're spot on with the observation that the OP's code is different from the one in their book, but I'm not sure how significant that is regarding the actual answer about the priorities that can be found on the duplicate(s).
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.