DEV Community

Cover image for A Sneak Peek At Asynchronous JavaScript
SR Sajjad
SR Sajjad

Posted on • Originally published at Medium on

A Sneak Peek At Asynchronous JavaScript

The word "Asynchronous" means  something will happen in the future without blocking other tasks.

Let's say we wrote some instructions with JavaScript.

A. do this
B. do this
C. do this
Enter fullscreen mode Exit fullscreen mode

A will be executed
then B
then C

Serially, common sense, right ?
But sometimes, it’s not the case. Let’s see -

let name = "Heisenberg"
Enter fullscreen mode Exit fullscreen mode

This variable name has a value. You want to print out this value.

console.log(name)
Enter fullscreen mode Exit fullscreen mode

What if this value isn’t available in your code. It’s somewhere else outside . Maybe some server serves this value when we send HTTP request. Or maybe it’s inside a file.

So it’s not in your code right now. Your program will have to fetch it from outside.

Now the code looks like this -

let name;

// some imaginary Service
// which sends us a String value as response
fetch("/saymyname")
  .then( res => res.text() )
  .then( value => name = value )

console.log(name)
Enter fullscreen mode Exit fullscreen mode

There’s a bug in the code.

The output would be - undefined.

name variable is still undefined. It wasn’t overridden as we wanted to do inside the fetch code.

That’s because JavaScript skips this fetching operation and continues executing the following lines of your code.

This fetching happens in the background by the Operating System and we get a Promise in our code that, when the resolved value will be available, we can use that data. And that’s why we’ll have to move our printing operation there too.

let name

fetch("/saymyname")
  .then( res => res.text() )
  .then( value => {
     name = value
     console.log(name)
   })
Enter fullscreen mode Exit fullscreen mode

We've just used some Async code.

Normally JavaScript is Synchronous. But there are some specific APIs in the language which are Asynchronous by nature. Like here we’ve used fetch API.

It’s a good thing because otherwise this program would freeze until the data is available for us.

But this is also problematic because it’s not a regular way of writing code, there’s an overhead of keeping async things in the sync. For this, we have a much cleaner API now  —  Async/Await. Which also blocks, but you get to control where and when you want to block.

Another thing we want to take advantage of is  —  Parallel Execution (Concurrent precisely). In our previous example,  if we had multiple fetching operations, they would happen in parallel. Thanks to the multi threading interface of Operating System.

To understand this let’s look at another example. Say we want to read text from 2 different files.

async function readFiles() {
  let text1 = await readFile('/fileOne.txt') // 3 seconds
  console.log("text from file one", text)

  let text2 = await readFile('/fileTwo.text') // 2 seconds
  console.log("text from file two", text)
}

readFiles()

console.log("Processing...")
Enter fullscreen mode Exit fullscreen mode

This looks nice, but this is blocking code. They are independent operations. So they should take only 3 seconds to process. But now they are taking 3 + 2 = 5 seconds.

So how to write this in parallel ?

Promise.all() - this API handles multiple independent Async operations in paralllel. And we can await for the whole process to finish.


const [text1, text2] = await Promise.all([
                          readFile('/fileOne.txt'),
                          readFile('/fileTwo.txt')
                        ]) // total 3 seconds

console.log("Done")
Enter fullscreen mode Exit fullscreen mode

Here, both file reading operations are parallel and also we get resolved values in sequence. This is great.

Except, this API short circuits. If any of these operations fails, the whole thing fails from that point. What if we want it to work as Microservice , meaning  -  an Async operation can fail, but we still want other operations’ resolved values, then we can’t use Promise.all(). Instead we need to use Promise.allSettled().

So now, we have this basic idea that there can be different requirements for Async operations and for handling them there are different variances of Promise API too. For example another useful one is Promise.race().

Event Loop

A promise can have 2 states. Pending and Resolved /Rejected.

A pending promise means  —  it’s currently being handled in the background.
A resolved promise means  —  it will be executed at the end of the running event loop.

On each iteration of event loop, we can consider 3 cases -

  1. If it’s a synchronous code, execute it.
  2. If it’s a pending Promise then skip it. It’s running in the background.
  3. If it’s a resolved(rejected) Promise, then the callback will run at the end of this particular iteration of event loop.

When the resolved Promise is available, it’s then-able. Meaning   we can attach a callback to work with the resolved data. So a resolved Promise can be available anytime inside a particular iteration of Event Loop. And the callback will be fired within this same iteration, but at the very end after finishing all the Synchronous works.

Let’s look at an interesting case -

setTimeout(()=> console.log('timeout'), 0)

Promise.resolve().then(()=> console.log('resolved promise'))

console.log('synchronous')
Enter fullscreen mode Exit fullscreen mode

We are emulating a resolved Promise here and also a timer. So on a running event loop phase, after finishing all the Sync code, it’s going to check

  • If there’s any callback of resolved Promise to run.
  • If there’s a timer callback to run.

So before the timer callback, it’s going to check if there’s any resolved promise. First they are going to be executed. It doesn’t matter how much time it takes, and in the mean time, there could be other resolved promises popped up in the current event loop. After finishing all of them, the timer callback gets executed finally.

That means, you can’t expect the timer to run after the exact interval you provided, like here we did- 0 ms. It might take longer than that.

So output of the code —

synchronous
resolved promise
timeout
Enter fullscreen mode Exit fullscreen mode

N.B. Different browsers can have different implementations. This is Chrome/Node standard behavior.

To understand how event loop actually works- read this- https://nodejs.org/uk/docs/guides/event-loop-timers-and-nexttick/

And a fantastic article by Jake Archibald on Task, Microtask scheduing -

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

That’s all folks. Have fun with your Asynchronous journey.

Oldest comments (0)