Register to get access to free programming courses with interactive exercises

Async/Await JS: Asynchronous programming

Despite all the conveniences, promises are not the pinnacle of programming evolution. Let's look at the drawbacks they add:

  • It has its error handling, which bypasses try/catch. It means that both types of error handling appear in the code in odd combinations.
  • Sometimes, you need to pass data down the chain from the highest levels, and it's not very convenient to do that with promises. We need to create variables outside the promise
  • It's still easy to end up with nested code when using promises if you're not paying attention.

All of these problems can be solved by the async/await mechanism, which makes code with promises look even more like synchronous code.

Let's recall our task of merging two files:

import fsp from 'fs/promises';

const unionFiles = (inputPath1, inputPath2, outputPath) => {
  let data1;
  return fsp.readFile(inputPath1, 'utf-8')
    .then((content) => {
      data1 = content;
    })
    .then(() => fsp.readFile(inputPath2, 'utf-8'))
    .then((data2) => fsp.writeFile(outputPath, `${data1}${data2}`));
};

Now let's look at the same code using async/await. Note that async/await works with promises:

import fsp from 'fs/promises';

const unionFiles = async (inputPath1, inputPath2, outputPath) => {
  // This is a major point. Just as in the example above, these requests execute strictly one after the other
  // It doesn't block the program, but it means that another code executes during these requests
  const data1 = await fsp.readFile(inputPath1, 'utf-8');
  const data2 = await fsp.readFile(inputPath2, 'utf-8');
  await fsp.writeFile(outputPath, `${data1}${data2}`);
};

This version looks almost exactly like the synchronous version. The code is so simple, it's hard to believe it's not synchronous. Let's break it down step by step.

The first thing we see is the keyword async before the function definition. It means that this function always returns a promise: const promise = unionFiles(...). And now we don't need to return the result of this function explicitly, it will become a promise anyway.

The keyword await is used inside the function before calling functions that also return promises. If the result of this call is assigned to a variable or constant, the result of the call is written to it. If there is no assignment to a variable or constant, as in the last await call, then the operation waits to be executed without using its result.

As with the promises, asynchrony in this case guarantees that the program won't lock up while waiting for calls to complete. It can continue doing something else, but not in this function. But it does not guarantee that things will happen in parallel.

Also, successive await calls within the same function always run in strict order. The easiest way to understand this is to think of the code as a chain of promises, where each successive operation takes place inside then.

What about error handling? Now all you have to do is add the usual try/catch and it catches the errors:

import fsp from 'fs/promises';

const unionFiles = async (inputPath1, inputPath2, outputPath) => {
  try {
    const data1 = await fsp.readFile(inputPath1, 'utf-8');
    const data2 = await fsp.readFile(inputPath2, 'utf-8');
    await fsp.writeFile(outputPath, `${data1}${data2}`);
  } catch (e) {
    console.log(e);
    throw e; // Throwing it again, because the code which calls the function should catch the errors
  }
};

However, when executing promises in parallel, you can't do without the Promise.all function:

const unionFiles = async (inputPath1, inputPath2, outputPath) => {
  // These calls start reading almost simultaneously and don't wait for each other
  const promise1 = fsp.readFile(inputPath1, 'utf-8');
  const promise2 = fsp.readFile(inputPath2, 'utf-8');
  // Now we wait for them both to finish
  // The data can be unpacked straight away
  const [data1, data2] = await Promise.all([promise1, promise2]);
  await fsp.writeFile(outputPath, `${data1}${data2}`);
};

In conclusion, the async/await mechanism makes the code as flat and synchronous as possible. It makes it possible to use try/catch and easily manipulate data from asynchronous operations:

// The code with callbacks
import fs from 'fs';

fs.readFile('./first', 'utf-8', (error1, data1) => {
  if (error1) {
    console.log('boom!');
    return;
  }
  fs.readFile('./second', 'utf-8', (error2, data2) => {
    if (error2) {
      console.log('boom!');
      return;
    }
    fs.writeFile('./new-file', `${data1}${data2}`, (error3) => {
      if (error3) {
        console.log('boom!');
      }
    });
  });
});

// The code with promises
import fsp from 'fs/promises';

let data1;
fsp.readFile('./first', 'utf-8')
  .then((d1) => {
    data1 = d1;
    return fsp.readFile('./second', 'utf-8');
  })
  .then((data2) => fsp.writeFile('./new-file', `${data1}${data2}`))
  .catch(() => console.log('boom!'));

// The code on async/await
import fsp from 'fs/promises';

// In real life, it's better to read files in parallel, like in this function 
const data1 = await fsp.readFile('./first', 'utf-8');
const data2 = await fsp.readFile('./second', 'utf-8');
await fsp.writeFile('./new-file', `${data1}${data2}`);

Recommended materials

  1. Real code example from Hexlet projects

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.