Micro-task queue and callback queue confusion

I’m following a course and have tried to break down this question in the code (see comments)

function translate (word) {
  const apiKey = '...'

  return 'https://www.googleapis.com' +
    '/language/translate/v2' +
    `?key=${apiKey}` +
    '&source=en' +
    '&target=fr' +
    `&q=${encodeURIComponent(word)}`
}

function sayHello () {
  console.log('Hello')
}

function parseData (response) {
  return response.json()
}

function logTranslation (parsedResponse) {
  console.log(
    parsedResponse
      .data
      .translations[0]
      .translatedText // 'Pourquoi'
  )
}

function delayScriptRunning () {
  for (let i = 0; i < 100000000; i++) {
    Math.random()
  }
}

// Let's start from here

// browser feature timer set and after ~1ms
// sayHello is added to the callback queue
// where it waits until the callstack is free
window.setTimeout(sayHello, 0)

// xmlHttpRequest request is sent to the given url
// a promise object is created with a
// value property set to undefined
// and an empty onfulfillment array
fetch(translate('why'))

// parseData function definition is immediately added to the onfulfillment array
  .then(parseData)

// logTranslation is also immediately added to the onfulfillment array
  .then(logTranslation)


// global execution is delayed by a couple of seconds
delayScriptRunning()

// during this delay the translate api sends back a response
// which is assigned to the promise's value property
// with the value being set onfulfillment is triggered and the callbacks
// 'parseData' and 'logTranslation' are added to the microtask queue


// 'Me First!' is logged to the console
console.log('Me first!')

// The callstack is now empty!!

// micro-task queue takes priority over the callback queue
// 'parseData' is added to the callstack, executed and removed on return
// 'logTranslation' is added to the callstack, executed and removed on return

// Finally the callstack is empty and sayHello can be passed to the callstack and executed

I suppose a version without comments might also be helpful

window.setTimeout(sayHello, 0)

fetch(translate('why'))
  .then(parseData)
  .then(logTranslation)

delayScriptRunning() // 2 seconds

console.log('Me first!')

Expected output

  1. ‘Me first!’
  2. ‘Pourquoi’
  3. ‘Hello’

Actual Output

  1. ‘Me first!’
  2. ‘Hello’
  3. ‘Pourquoi’

I guess the confusion for me centres around the onfulfillment array and also .json()

Are the two thenables added to the onfulfillment array and in turn both sent to the micro-task queue on a value being set?

What is json() doing that enables sayHello to be executed?

Wood for the trees is coming to mind.

The way that I understand this is: setTimeout that the fetch first takes place, but takes a long time to happen because of network communication delays, and after the delay loop as part of the script execution Me first! is immediately shown.

After that there is no guarantee of which order that things occur in. It can depend on the browser and differing network speeds.

In your case the setTimeout triggered first before the fetch was resolved which makes sense, as it takes a long time (comparatively for computers) for the fetch request to be sent to Google and for the translation to occur, before being sent back to you.

1 Like

In theory ‘Me first!’ will always come first, as the microtask queue has to wait for the global execution to complete e.g. for the callstack to be empty

I have read on stackoverflow about the network, but in that instance the delay the person was using was only a short for loop of what a few milliseconds.

I meant to add this example where we take the .json() call out of the equation

window.setTimeout(sayHello, 0)

fetch(translate('why'))
  .then(response => response)
  .then(console.log)

delayScriptRunning() // 2 seconds

console.log('Me first!')

In this example it does run in a predictable order

  1. ‘Me first!’
  2. response object
  3. ‘Hello’

So this points to the json call, which I believe is asynchronous. Where my brain starts to melt, is I can picture parseData being added to the callstack, but what happens on it’s return, what happens to logTranslation?

I guess just to complicate things we need to take into account console.log is asynchronous too

The settimeout does trigger almost immediately, but then sayHello has to sit in the callback queue, which essentially has to wait for the global execution to complete and anything sat in the microtask queue in theory

Thanks man, your help is appreciated and I will look into the network queue side of it

Okay, let’s delve a bit deeper into the following part:

fetch(translate('why'))
  .then(parseData)
  .then(logTranslation)

You have three promises being set up there. One for fetch, and two for then.

The fetch line results in a promise. The code in that translate function gives a string url. That promise is resolved when the request to that url is resolved, resulting in an HTTP response.

The first .then line sets up a second promise, waiting for the fetch function to be resolved. When it is resolved the HTTP response is sent to the parseData function.

The second .then line sets up a third and final promise, waiting for the second promise to be resolved before passing the result to logTranslation.

I’m currently distracted by a few dozen people around me, but hopefully this helps.

Absolutely!

Maybe I’m not understanding you clearly (very possible), but this is kind of at odds with the instructors breakdown of events. In fact he has a serious dislike of the word ‘then’, preferring ‘store-function-we-want-to-autotrigger-on-value-property-being-updated’ — catchy!

In his breakdown all ‘then’ does is push the full function definition in to the onfulfillment array. That’s it job done! ‘then’ does not come into play at a later stage.

I am going to have to go and read up on this Paul, maybe from some other sources.

Thanks again :slight_smile:

Sure thing - here’s a good resource for that.

1 Like

Hey @rpg_digital, you can actually verify that it’s just the network by mocking fetch to resolve with a response immediately:

function fetch () {
  const body = JSON.stringify({
    data: {
      translations: [{
        translatedText: 'Pourquoi'
      }]
    }
  })

  return Promise.resolve(new Response(body))
}

// ... your code from post #1

Then you’ll get the output:

  1. Me first!
  2. Pourquoi
  3. Hello
2 Likes

Still reading…

The definitive javascript guide, does give me a clue to some of the confusion around my earlier test e.g.

fetch(translate('why'))
  .then(response => response)
  .then(console.log)

// 1. ‘Me first!’
// 2. fulfilled response object
// 3. ‘Hello’

It appears I am mistaking a fulfilled response with a resolved one

A promise is fulfilled when the HTTP status and the headers are available, and albeit it provides .text and .json methods which have access to the body, it doesn’t guarantee that the body of the response has arrived. So when we do

.then(response => response.json()) // promise returned
.then(parsed => something)

a promise is returned

Thanks for the help and apologies for being so stubbornly dense. lol

Note it is still kind of frustrating that other sources don’t really seem to go into enough depth. No mention of the network queue or the fulfillment array, just a ‘trust me’ approach. more to read… :slight_smile:

1 Like

Hm no that’s actually the same… as described here in the specs:

Any Promise object is in one of three mutually exclusive states: fulfilled , rejected , and pending

Trying it out in the browser console:

console.log(new Promise(() => null)) // Promise { "pending" }
console.log(Promise.resolve())       // Promise { "fulfilled" }
console.log(Promise.reject())        // Promise { "rejected" }
1 Like

That tends to be what specifications are for. From the link I provided earlier, the specs on what Fetch actually does should give you enough reading material to work with.

1 Like

Yes I have since read that in numerous articles. Thanks for the correction. My mistake

1 Like

I did skim through it yesterday (edit: I am confusing the link with this one https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). I have so many articles I am trying to read through at the moment. I will come back to it. Cheers :slight_smile:

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.