DEV Community

Cover image for Asynchronous vs Synchronous Programming
Scott Hardy
Scott Hardy

Posted on

Asynchronous vs Synchronous Programming

Time is an illusion. Lunchtime doubly so.

  • Douglas Adams, The Hitchhiker's Guide to the Galaxy

Introduction

In programming, synchronous operations block instructions until the task is completed, while asynchronous operations can execute without blocking other operations. Asynchronous operations are generally completed by firing an event or by calling a provided callback function.

Breaking Down JavaScript

Javascript has a:

  • Callstack
  • WebAPI
  • Event Loop
  • Callback Queue

The Callstack is the immediate work your program will do.

let i = 0 // declare a mutable variable
i += 1 // add one to the variable
console.log(i) // log the variable
Enter fullscreen mode Exit fullscreen mode

In the above example, declaring a variable, adding one to the variable, and logging the variable are all three separate instructions that get added to the Callstack. WebAPI's are methods available from environments where JavaScript is run. In browsers, the window and its methods are apart of the WebAPI. When the WebAPI completes, it puts the callback on the Callback Queue.

The Event Loop waits for the Callstack to complete the loaded work. Once the Event Loop notices the Callstack is clear it will add work to the Callstack from the Callback Queue. Consider window.setTimeout with a timer of 0 and a callback function that uses a variable before it is declared.

window.setTimeout(() => console.log(i), 0)
let i = 0 
i += 1
Enter fullscreen mode Exit fullscreen mode

Instead of an error, we get the correct answer 1, this is because of the function that uses console.log is a parameter to the first WebAPI instruction window.setTimeout. The callback function is moved to the Callback Queue when the timer completes. Once the Callstack clears declaring and adding one to the variable, our function is called and it is safe to use the variable.

Starting with callbacks

A callback executes once it is added to the Callstack. In the previous example, this relied on a timer to complete, however other APIs will have other conditions. A NodeJs example:

const fs = require('fs')
const content = 'Logging to a file'
fs.writeFile('test.txt', content, err => {
    if (err) {
        throw err
    }
    console.log('logs completed')
})
console.log('end script')
Enter fullscreen mode Exit fullscreen mode

The callback is called once the writeFile API has completed:

  • opening or creating the file
  • writing to the file
  • closing the file at the location specified

fs.writeFile is asynchronous so console.log('end script') is called before the work writeFile completes.

What changes are needed to do this action synchronously?

const fs = require('fs')
const content = 'Logging to a file'
try {
    fs.writeFileSync('test.txt', content)
    console.log('logs completed')
} catch (err) {
    throw err
}
Enter fullscreen mode Exit fullscreen mode

A try {} catch() {} and the use of the synchronous write file function writeFileSync. If err is thrown the console.log is not called.

Synchronous Operations

Synchronous Operations that run block the next operation until it completes. Blocking operations may not always seem like an issue because computers are fast. For example: creating an array and logging the values in an array.

Array
    .from({ length: 5 }, (v, i) => i + 1)
    .forEach(value => console.log(value))
Enter fullscreen mode Exit fullscreen mode

However if the length was 5000 it would take longer before the array was logged. The difference in timing is a result of the thread being locked for a longer time.
Making synchronous calls to resources can lead to long response times locking up the UI until the resource responds. As an example:

const request = new XMLHttpRequest()
request.open('GET', 'https://httpstat.us', false)
request.send(null)

if (request.status === 200) {
  console.log(request.responseText)
}
Enter fullscreen mode Exit fullscreen mode

Making requests to your own services like a database can have the same effect. A common webpage will have many requests to make under unique circumstances, and as a developer, you'll want those requests to start as soon as possible but still allow the rest of the page to load what it can to enable the requests.
This is when asynchronous operations become powerful.

Asynchronous Operations

Asynchronous Operations happen independently from the main program flow. A common use for asynchronous code is querying a database and using the result. Passing in a callback is a way to interact with the response or error.

const database = require('thecoolestnewestdbframework')
database('table')
    .select('*')
    .asCallback((err, res) => {
        if (err) {
            throw err
        }
        // do something with the result
    })
Enter fullscreen mode Exit fullscreen mode

While the database loads and responds to the request, the rest of the page or other resources cannot load.

Promises and Asynchronous Operations

Promises are another way to interact with asynchronous code. In the above example if the const database returned a Promise then we could write:

const database = require('thecoolestnewestdbframework')
database('table')
    .select('*')
    .then(res => {
        // do something with the result
    })
    .catch(err => throw err)
Enter fullscreen mode Exit fullscreen mode

A Promise represents the work that is happening asynchronously. When the Promise resolves the result can be caught as an error, or used in a then method. then returns a Promise, this means then is chainable returning another Promise to the next then.

const database = require('thecoolestnewestdbframework')

database('table')
    .select('*')
    .then(res => {
        // do something with result
        return somethingDifferent
    })
    .then(res => {
        return database('other_table')
            .select('*')
            .where('id', res)
    })
    .then(res => {
        // do something else
    })
    .catch(err => throw err)
Enter fullscreen mode Exit fullscreen mode

Top comments (9)

Collapse
 
anwarus profile image
Sebastian Milos

Nice post. I still sometimes find myself embarrassed on how some code snippets in JS works but this explaining a bit of what's under the hood and make it more logical :)

Collapse
 
hardy613 profile image
Scott Hardy

LOL - I know the feeling. I get embarrassed looking at the code I wrote six months ago.

Collapse
 
toastking profile image
Matt Del Signore

Nice post! I find a lot of people don't fully understand how asynchronous code is different from stuff like multithreading.

Collapse
 
hardy613 profile image
Scott Hardy

Thanks, Matt!

How would you explain to someone misunderstanding the difference with Asynchronous code and Multithreaded code?

Collapse
 
toastking profile image
Matt Del Signore

The way I usually explain it is that threads can be though of as parallel lines doing work simultaneously. Asynchronous code can be thought of as a line with a bunch of different segments one after the other. Asynchronous hands of execution over and over.

Collapse
 
hardy613 profile image
Scott Hardy • Edited

The post is hosted publicly here: github.com/hardy613/es6-notes/blob.... The project is FOSS and PR's are welcome if you notice an error or would like to make an addition.

Collapse
 
vipinkumarsn4 profile image
Vipin Saini

Great Post. I am surprised how deeply you are aware about the flow of javascript behind the scene.
Honestly, thanks to your post so that i can learn something really good.

Collapse
 
hardy613 profile image
Scott Hardy

Thank you for the kind words!

Collapse
 
promiser profile image
parmeshwar rathod • Edited

thanx for such post its really usefull for me
thanking you again