Geekflare is supported by our audience. We may earn affiliate commissions from buying links on this site.
In Development Last updated: August 11, 2023
Share on:
Invicti Web Application Security Scanner – the only solution that delivers automatic verification of vulnerabilities with Proof-Based Scanning™.

When writing JavaScript applications, you may have encountered asynchronous functions, such as the fetch function in the browser or the readFile function in Nodejs.

You may have gotten unexpected results if you used either of these functions like you normally would. This is because they are asynchronous functions. This article guides what that means and how to use asynchronous functions like a pro.

Introduction to Synchronous Function

JavaScript is a single-threaded language that can only do one thing at a time. This means that if the processor encounters a function that takes a long time, JavaScript waits until the whole function has been executed before moving on to other parts of the program.

Most functions are executed wholly by the processor. This means that during the execution of the said functions, regardless of how long it takes, the processor will be completely busy. These are called synchronous functions. An example synchronous function has been defined below:

function add(a, b) {
    for (let i = 0; i < 1000000; i ++) {
        // Do nothing
    }
    return a + b;
}

// Calling the function will take a long time
sum = add(10, 5);

// However, the processor cannot move to the 
console.log(sum);

This function performs a large loop whose execution takes a long time before returning the sum of its two arguments.

After defining the function, we called it and stored its result in the sum variable. Next, we logged the value of the sum variable. Even though executing the add function takes a while, the processor cannot move on to log the sum until the execution is complete.

A screen shot of a python program.

The vast majority of functions you will encounter will behave in predictable ways like the one above. However, some functions are asynchronous and do not behave like regular functions.

Introduction to Asynchronous Function

Asynchronous functions do the majority of their work outside the processor. This means that even though the function might take a while to complete execution, the processor will be unoccupied and free to do more work.

Here’s an example of such a function:

fetch('https://jsonplaceholder.typicode.com/users/1');

To enhance efficiency, JavaScript enables the processor to move on to other tasks that require the CPU even before the asynchronous function’s execution is complete.

Since the processor moved on before the asynchronous function’s execution was complete, its result will not be immediately available. It will be pending. If the processor tried to execute other parts of the program that depended on the pending result, we would get errors.

Therefore, the processor should only execute parts of the program that do not depend on the pending result. To do this, modern JavaScript utilizes promises.

What is a Promise in JavaScript?

In JavaScript, a promise is a temporary value that an asynchronous function returns. Promises are the backbone of modern asynchronous programming in JavaScript.

After a promise is created, either one of two things happens. It either resolves when the asynchronous function’s return value is successfully produced or rejects in the event of an error. These are events within a promise’s lifecycle. Therefore, we can attach event handlers to the promise to be called when it resolves or rejects.

All the code that requires the final value of an asynchronous function can be attached to the promise’s event handler for when it resolves. All the code that handles the error of a rejected promise will also be attached to its corresponding event handler.

Here’s an example where we read data from a file in Nodejs.

const fs = require('fs/promises');

fileReadPromise = fs.readFile('./hello.txt', 'utf-8');

fileReadPromise.then((data) => console.log(data));

fileReadPromise.catch((error) => console.log(error));

In the first line, we import the fs/promises module.

In the second line, we call the readFile function, passing in the name and encoding for the file whose contents we want to read. This function is asynchronous; therefore, it returns a promise. We store the promise in the fileReadPromise variable.

In the third line, we attached an event listener for when the promise resolves. We did this by calling the then method on the promise object. As an argument to our call to the then method, we passed in the function to run if and when the promise resolves.

In the fourth line, we attached a listener for when the promise rejects. This is done by calling the catch method and passing in the error event handler as an argument.

A screenshot of an async javascript program.

An alternative approach is to use the async and await keywords. We will cover this approach next.

Async and Await Explained

Async and Await keywords can be used to write asynchronous Javascript in a way that looks better syntactically. In this section, I will explain how to use the keywords and what effect they have on your code.

The await keyword is used to pause the execution of a function while waiting for an asynchronous function to complete. Here’s an example:

const fs = require('fs/promises');

function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

readData()

We used the await keyword while making a call to readFile. This instructed the processor to wait until the file has been read before the next line (the console.log) can be executed. This helps ensure that code that depends on an asynchronous function’s result will not be executed until the result becomes available.

If you tried to run the above code, you would encounter an error. This is because await can only be used inside an asynchronous function. To declare a function as asynchronous, you use the async keyword before the function declaration like so:

const fs = require('fs/promises');

async function readData() {
	const data = await fs.readFile('./hello.txt', 'utf-8');

    // This line will not be executed until the data becomes available
	console.log(data);
}

// Calling the function so it runs
readData()

// Code at this point will run while waiting for the readData function to complete
console.log('Waiting for the data to complete')

Running this code snippet, you will see that JavaScript executes the outer console.log while waiting for the data read from the text file to become available. Once it’s available, the console.log inside readData is executed.

A screen shot of an async python script on a computer.

Error handling while using the async and await keywords is usually performed using try/catch blocks. It is also important to know how to loop with asynchronous code.

Async and await are available in modern JavaScript. Traditionally, asynchronous code was written through the use of callbacks.

Introduction to Callbacks

A callback is a function that will be called once the result is available. All code that requires the return value will be put inside the callback. Everything else outside the callback does not depend on the result and is, therefore, free to be executed.

Here is an example that reads a file in Nodejs.

const fs = require("fs");

fs.readFile("./hello.txt", "utf-8", (err, data) => {

	// In this callback, we put all code that requires 
	if (err) console.log(err);
	else console.log(data);
});

// In this part here we can perform all the tasks that do not require the result
console.log("Hello from the program")

In the first line, we imported the fs module. Next, we called the readFile function of the fs module. The readFile function will read text from a file we specify. The first argument is which file that is, and the second specifies the file format.

The readFile function reads the text from files asynchronously. To do this, it takes in a function as an argument. This function argument is a callback function and will be called once the data has been read.

The first argument passed when the callback function is called is an error that will have a value if an error arises while the function is running. If no error is encountered, it will be undefined.

The second argument passed to the callback is the data read from the file. The code inside this function will access the data from the file. Code outside this function does not require data from the file; therefore can be executed while waiting for data from the file.

Running the above code would produce the following result:

A screen shot of an async python script on a computer.

Key JavaScript Features

There are some key features and characteristics that influence how async JavaScript works. They are well explained in the video below:

YouTube video

I have briefly outlined the two important features below.

#1. Single-threaded

Unlike other languages that allow the programmer to use multiple threads, JavaScript only allows you to use one thread. A thread is a sequence of instructions that logically depend on one another. Multiple threads allow the program to execute a different thread when blocking operations are encountered.

However, multiple threads add complexity and make understanding the programs that use them harder. This makes it more likely that bugs will be introduced in the code, and it would be hard to debug the code. JavaScript was made single-threaded for simplicity. As a single-threaded language, it relies on being event-driven to handle blocking operations efficiently.

#2. Event-driven

JavaScript is also event-driven. This means that some events happen during a JavaScript program’s lifecycle. As a programmer, you can attach functions to these events, and whenever the event happens, the attached function will be called and executed.

Some events could result from a blocking operation’s result being available. In this case, the associated function is then called with the result.

Things to Consider When Writing Asynchronous JavaScript

In this last section, I will mention some things to consider when writing asynchronous JavaScript. This will include browser support, best practices, and importance.

Browser Support

This is a table showing the support of promises in different browsers.

A player's stats displayed in a game using Javascript.
Source: caniuse.com

This is a table showing the support of async keywords in different browsers.

A screen shot displaying multiple numbers generated by a JavaScript async function.
Source: caniuse.com

Best Practices

  • Always opt for async/await, as it helps you write cleaner code that is easy to think about.
  • Handle errors in try/catch blocks.
  • Use the async keyword only when it is necessary to wait for the result of a function.

Importance of Asynchronous Code

Asynchronous code enables you to write more efficient programs that only use one thread. This is important as JavaScript is used to build websites that make lots of asynchronous operations, such as network requests and reading or writing files to disk. This efficiency has enabled runtimes like NodeJS to grow in popularity as the preferred runtime for application servers.

Final Words

This has been a lengthy article, but in it, we were able to cover how asynchronous functions differ from regular synchronous functions. We also covered how to use asynchronous code using just promises, async/await keywords, and callbacks.

In addition, we covered key features of JavaScript.In the last section, we wrapped up by covering browser support and best practices.

Next, check out Node.js’ frequently asked interview questions.

  • Anesu Kafesu
    Author
    Full stack web developer and technical writer. Currently learning AI.
  • Rashmi Sharma
    Editor

    Rashmi has over 7 years of expertise in content management, SEO, and data research, making her a highly experienced professional. She has a solid academic background and has done her bachelor’s and master’s degree in computer applications…. read more

Thanks to our Sponsors
More great readings on Development
Power Your Business
Some of the tools and services to help your business grow.
  • Invicti uses the Proof-Based Scanning™ to automatically verify the identified vulnerabilities and generate actionable results within just hours.
    Try Invicti
  • Web scraping, residential proxy, proxy manager, web unlocker, search engine crawler, and all you need to collect web data.
    Try Brightdata
  • Monday.com is an all-in-one work OS to help you manage projects, tasks, work, sales, CRM, operations, workflows, and more.
    Try Monday
  • Intruder is an online vulnerability scanner that finds cyber security weaknesses in your infrastructure, to avoid costly data breaches.
    Try Intruder