Register to get access to free programming courses with interactive exercises

Promises JS: Asynchronous programming

Asynchronous code, for all its advantages, is very difficult to analyze. It takes just a few nested callbacks with parallel operations for it to be almost impossible to figure out what's going on. It's scary to imagine a large program in which everything is asynchronous. A thousand or so lines of code and no one can understand it.

From the beginning, the developers understood the limitations of the callback approach, but it took a long time before an alternative, called Promises, appeared in JavaScript. Promises change the way the code is organized without adding new syntax. If used correctly, they allow you to “straighten out” asynchronous code and make it consistent and flat.

Most of today's JavaScript code is written using Promises, and callbacks are a thing of the past. For example, the developers of Node.js have implemented promises in almost all embedded modules. The functions that are built on promises and designed for working with file systems are available through the fs module's promises property. Compare a few examples:

import fs from 'fs';

// The callback-based code looks kind of like a staircase

fs.readFile('./first', 'utf-8', (_error1, data1) => {
  console.log(data1);
  fs.readFile('./second', 'utf-8', (_error2, data2) => {
    console.log(data2);
    fs.readFile('./third', 'utf-8', (_error3, data3) => {
      console.log(data3);
    });
  });
});

// The code based on promises is almost flat

// Rename promises property to fsp for brevity
const { promises: fsp } = fs;

fsp.readFile('./first', 'utf-8')
  .then((data1) => console.log(data1))
  .then(() => fsp.readFile('./second', 'utf-8'))
  .then((data2) => console.log(data2))
  .then(() => fsp.readFile('./third', 'utf-8'))
  .then((data3) => console.log(data3));

Technically, a promise is a special object that keeps track of an asynchronous operation and stores its result inside itself. It's returned by all asynchronous functions built on promises.

const promise = fsp.readFile(src, 'utf-8');

It's vital to understand that a promise is not the result of an asynchronous operation. It's an object that keeps track of the operation. The operation is still asynchronous and will be executed sometime later.

const promise = fsp.readFile(src, 'utf-8');
// The file hasn't yet been read
console.log(promise);
// Promise { <pending> }
// pending is a promise state, it indicates that the operation is still in progress

How do I get the result of an asynchronous operation? There's no way to do it from the outside, it's just impossible. But you can “continue” the promise using the then() method, within which you need to pass a callback function. The parameter of this callback will be the result of the asynchronous operation

// the result of reading the file is passed to the callback function passed to then()
// the callback will only be called when the file is read
fsp.readFile(src, 'utf-8').then((content) => console.log(content));

The callback is passed inside then(), not called. The promise itself makes the call when the asynchronous operation is executed.

Regardless of the contents of the callback function, calling then() always returns a new promise. And the callback function's return becomes available as a parameter of the callback of the next then(). It's this organization of promises that allows you to build chains without having to put calls into each other, thereby avoiding Callback Hell.

// Suppose there was some text saying “Hexlet” inside the file
const promise = fsp.readFile(src, 'utf-8') // the result of the chain is ALWAYS a promise
  .then((content) => `go to the next then with ${content}`) // ignore the result of the operation
  .then((text) => console.log(text)); // the value from the previous then is passed into this callback, whose role is played by the log
// => go to the next then with Hexlet
// Self-check.
// What will be displayed if we add then(console.log) to the promise above?

What if the callback function returns a promise instead of just a value? Then the parameter of the next then() will be the result of this promise. Otherwise, the promises would be meaningless. Example:

const promise = /* some kind of operation here */
  // From callback comes back promise
  .then(() => fsp.readFile(filePath))
  // The result of the previous promise goes into the callback
  .then((text) => console.log(text));

Using promises inside any function automatically makes that function asynchronous. It will no longer be possible for it to be called as a normal synchronous function because then it won't be possible to use its result, nor will it be possible to wait for the operation to complete or find errors:

// Incorrect definition

export const copy = (src, dest) => {
  fsp.readFile(src, 'utf-8')
    .then((content) => fsp.writeFile(dest, content));
};

// Usage

// doing something synchronous

copy(src, dest);

// do something else

As soon as there is any asynchrony in the code, the code needs to change its structure. If it's callbacks, then it gets more nested, and if it's promises, then the whole code turns into a continuous chain of promises.

// Correct Definition

export const copy = (src, dest) => {
  return fsp.readFile(src, 'utf-8')
    .then((content) => fsp.writeFile(dest, content));
};

// Usage

// doing something synchronous

copy(src, dest).then(() => {
  // do something else
}).then(/* continue */)
  .then(/* continue */);

Incorrect use of promises

The main advantage of promises over callbacks is that with them, asynchronous code becomes a bit like synchronous code. You can see the chain of calls, and it doesn't get deeper and deeper. At least in theory. In practice, however, promises aren't always used correctly. Look at this code:


fsp.readFile('./first', 'utf-8')
  .then((data1) => {
    console.log(data1);
    // 
    // Read the file and continue the promise from this internal function
    return fsp.readFile('./second', 'utf-8').then((data2) => {
      console.log(data2);
      // Read the file and continue the promise from this internal function
      return fsp.readFile('./third', 'utf-8').then((data3) => {
        console.log(data3);
      });
    });
  });

Despite the fact that promises are used here, the code looks even more complicated than with callbacks. The problem is the way the calls are set up. The continuation of the chain does not come from the highest promise, but from each subsequent asynchronous operation. There are nested promises in theory, but only when there's no other way. In any other situation, the code should be flat and simple:

// A flat chain of promises
fsp.readFile('./first', 'utf-8')
  .then((data1) => console.log(data1))
  .then(() => fsp.readFile('./second', 'utf-8'))
  .then((data2) => console.log(data2))
  .then(() => fsp.readFile('./third', 'utf-8'))
  .then((data3) => console.log(data3));

Recommended materials

  1. promisejs.org

Hexlet Experts

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

The Hexlet support team or other students will answer you.

About Hexlet learning process

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:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

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.