Combined Javascript Questions

Over the last few days, I've been going through the fantastic Javascript Questions repo to see what parts of javascript I can discover and understand better. I got 86/156 correct with a 44.9% error rate, but at least I passed!

Most of my mistakes were due to the general lack of knowledge about basic mechanics of functions and javascript classes, but there were a few I felt were interesting because they were pretty new and strange to me.

I'm still wrapping my head over generators as I don't use them much, except in redux-saga. Other things I thought were pretty useful to understand was the javascript event loop.

In this post, I share a question as a code snippet that I put together to illustrate and try to explain some of the things that were strange to me.

My question

What is the output of running this?

async function* strangeFunction(...args) {
  for (arg of args) {
    yield Promise.resolve(arg)
  }
}

const myPromise = async () => {
  const returnData = []
  const strangeData = strangeFunction`${'1'}2${'3'}4`
  for await (const data of strangeData) {
    console.log(data)
    returnData.push(data)
  }
  return { returnData }
}

function funcOne() {
  myPromise()
  setTimeout(() => console.log('Timeout!', 1))
  console.log('funcOne Last line!')
}

async function funcTwo() {
  let res = await myPromise()
  res[Symbol.iterator] = function* () {
    yield* Object.values(this)
  }
  console.log([...res])
  setTimeout(() => console.log('Timeout!', 2))
  console.log('funcTwo Last line!')
}

funcOne()
funcTwo()

Take a second to see if there are any strange syntax you are not familiar with, then go on to guess what the output would be...

Ready? And the answer is......

The answer

funcOne Last line!
[ '', '2', '4' ]
[ '', '2', '4' ]
1
1
3
3
[ [ [ '', '2', '4' ], '1', '3' ] ]
funcTwo Last line!
Timeout! 1
Timeout! 2

The explanation

The first thing to notice is that funcOne() and funcTwo() are the functions that gets things started. Since the functions are called in sequence, you might think that their outputs will be in order. However, things are not straightforward when you have promises and async/await. While lines on a script generally do occur synchronously on the main thread, whenever something asynchronous is set to happen, it will be put aside on another queue to get it done.

Event loop

To understand why 'funcOne Last line!' was printed first, we need to know that when funcOne() runs, myPromise() is called but isn't executed since it is an asynchronous function. Instead, it is queued up, waiting for execution. Similarly, the callback in setTimeout is queued. The difference is under what category they are queuing in. myPromise is queued as a microtask, but the setTimeout callback is queued as a task, that is, after both funcOne and funcTwo have completed their execution. myPromise, on the other hand, does not wait so long, and completes execution as the function ends. This explains why both setTimeout callbacks appear only at the last lines.

To learn more, check out this post with nice animations to help understand the event loop

function funcOne() {
  myPromise()
  setTimeout(() => console.log('Timeout!', 1))
  console.log('funcOne Last line!')
}

Strange stuffs (to me)

Before we carry on, there are some strange language features to explain. This line

const strangeData = strangeFunction`${'1'}2${'3'}4`

String literals

made me think that some typo had gone into the repo. Turns out it is an actual syntax! How it works is better explained in code:

const someFunction = (...args) => {
  console.log(args)
}
someFunction`${'1'}2${'3'}4`
// Returns
// [ [ '', '2', '4' ], '1', '3' ]

This syntax splits the string literal into a few arguments, and is meant to be a way to allow custom formatting of the string literal. The first argument represents all the non-variables, and the following arguments are the variable values that were inside the string literal. If you replaced the commas in the first argument with the variables, you would get the expected string.

Asynchronous generators

async function* strangeFunction(...args) {
  for (arg of args) {
    yield Promise.resolve(arg)
  }
}

const myPromise = async () => {
  const returnData = []
  const strangeData = strangeFunction`${'1'}2${'3'}4`  < #1
  for await (const data of strangeData) {              < #2
    console.log(data)
    returnData.push(data)
  }
  return { returnData }
}

At #1, strangeData is assigned an asynchronous generator. At #2, await is used with a for..of loop, which resolves to promises that strangeData generates.

Remember I said I wasn't too good with generators? What about asynchronous generators? Craziness of course! Generators give you a value each time you call a generator, it returns the next value to yield. Similarly, each time you call an asynchronous generator, a Promise is yield.

Each time a promise completes, the next promise is generated. All this happens at await myPromise(), when waiting for the second asynchronous generator to complete. Since there are two generators at work, and scheduled one after the other, we see the the values as they generate the same things in each cycle.

[ '', '2', '4' ]
[ '', '2', '4' ]
1
1
3
3

Iterators

Objects are not iterable by default. That is, you cannot direct run a for loop on them. Which also means they are incompatible with the [...someObject] syntax, which works with arrays. It turns out that there is a property that can make objects iterable, and is used to make the array spread work in this part of the code.

res[Symbol.iterator] = function* () {
  yield* Object.values(this)
}
console.log([...res])

Summary

While we may never code with some of these features, I think understanding the Event Loop well will help make debugging timing related issues and prevent them from happening in the first place.

I am still not so familiar with these concepts, and perhaps my explanations are wrong. If they are I will be sure to refine them as I figure them out!

Cheers,

Jerome