Register to get access to free programming courses with interactive exercises

Asynchronous code JS: Asynchronous programming

The examples in this lesson are based on the file system because they best capture the essence of asynchronous programming. The principles of asynchronous code are identical for both front-end and back-end programmers.

In synchronous code, functions execute in the same place where and when we call them.

It's different in asynchronous code. Calling a function doesn't mean that it'll run there and then. Moreover, we don't know when it will work. Let's look at an example where a file is copied by reading and writing it again to another file:

import fs from 'fs';

// Make sure to pass the second parameter `utf-8`,
// only then the data will be returned in string form
const content = fs.readFileSync('./myfile', 'utf-8');
fs.writeFileSync('./myfile-copy', content);

The example code above was written for Node.js. It uses the fs module and its synchronous functions to read and write the file, and their peculiarity is that the names end with Sync.

This code works as expected. First, it reads the contents of a file into the constant content and then writes them to another file. Each line causes a block, so the program doesn't execute until the operating system has read the file and gives its contents to the program. Only then the next line executes.

Accordingly, when writing takes place, the program waits for the operating system to write the file to disk, and only then does it proceed.

In general, we could have stopped here. Why do anything else with this code?

The point is that any file operation will take a long time. They are thousands of times slower than calling a normal function. During this time, the process waits for a response from the kernel about the result of the operation without doing anything else. Therefore, the synchronous approach in working with file systems is inefficient in utilizing resources.

Asynchronous code, on the other hand, continues to run during all file operations. In other words, the code never stops on IO operations, but it can find out that they have been completed. In appropriate situations, properly written asynchronous programs are much more efficient than synchronous programs. Sometimes, this is so critical that the synchronous version can't accomplish the job.

Note: IO means input/output. It includes not only the handling of files but also all networking). Even printing to the screen is also writing to a file.

Now let's try to understand how asynchronous code works. Let's assume that the readFile function in the example below is asynchronous. It means that it reads the file not where we called it, but somewhere else in the background:

import fs from 'fs';

// It's an empty function, we'll analyze its meaning a bit later
// The asynchronous version of readFile requires a third parameter to be passed to the function
const noop = () => {};
const content = fs.readFile('./myfile', 'utf-8', noop);
console.log(content);

Is this asynchronous code possible? The answer is no. It doesn't matter what specific task this function performs. The only important thing is that it doesn't perform it immediately, which means that we can't return the result of an asynchronous operation.

If we run similar code, we can see the following output:

node index.js
undefined

It is a fundamental feature of asynchronous functions that you should learn. We have to emphasize this point because beginners constantly stumble on it, trying to work with asynchronous functions the same way as with synchronous ones.

Then this question arises: How do you get the result of this function? And for this, we use another function, which is passed to an asynchronous function. As such, it's called a callback.

This function will be called when the operation is over (possibly with an error). It has the following signature: callback(error, result).

There are two parameters:

  • The error, which is passed if things don't go well
  • The result of the operation, which is passed if everything was OK

We'll discuss errors later, but now let's review the general principles of work:

import fs from 'fs';

// It's called when an asynchronous operation is executed
const callback = (_error, data) => console.log(data);
// the lower underscore prefix indicates an unused argument

// readFile starts the file reading task.
// Make sure to pass utf-8 as the second parameter
// Only then will the data be read in string form
fs.readFile('./myfile', 'utf-8', callback);

Let's observe running and output:

node index.js
content of file

As soon as the file reading operation finished, the Node.js interpreter internally called a callback and passed the file's contents to it as a parameter.

It remains to be seen if this code is asynchronous:

import fs from 'fs';

const callback = (_error, data) => console.log(data);
console.log('before read');
// The function call doesn't wait for the file to finish reading, the code immediately continues to execute
fs.readFile('./myfile', 'utf-8', callback);
console.log('after read?');

And again, let's observe its running and output:

node index.js

before read
after read?
content of file

In the example above, after read? comes out with the last instruction. Despite this, the actual output varies from the order of instructions within the code. The asynchronous function starts executing immediately, but the callback is only triggered when no functions are left in the current call stack.

In our case, the callback starts only after the entire file has been worked on. And this run will spawn its own call stack.

In asynchronous code, each callback of an asynchronous function generates its call stack, which, in turn, can make new asynchronous calls and so on ad infinitum.

Node.js waits for the completion of all asynchronous calls that have been made while the program was running:

import fs from 'fs';

fs.readFile('./myfile', 'utf-8', (_error, data) => console.log('First!'));
fs.readFile('./myfile', 'utf-8', (_error, data) => console.log('Second!'));

In the example above, we can see two asynchronous operations running. We now know that the second reading of the file will run almost simultaneously with the first since the operations are asynchronous and their execution does not block the execution of the program. Try to answer this question: In what order will the results appear?

Here we see running and output:

node index.js

Second!
First!

node index.js

First!
Second!

As you can see, we can't answer this question definitively. Asynchronous operations can run in any order as long as they run simultaneously. And the only way to organize them is to run them sequentially; we'll talk about that later.

Asynchronous programming is much more complicated than synchronous programming. It is hard to observe linear code (sequentially when written) and think about it non-linearly.

Later, you'll see that as the number of asynchronous functions within one program increases exponentially, you may stop understanding what's going on.Many ways have been invented to combat it, some of which have proved to be successful. We will discuss them in later lessons in the course.


Recommended materials

  1. synchronous readFileSync function from fs module
  2. asynchronous readFile function from fs module

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.