JS: Asynchronous programming

Theory: new Promise

If there are promises in the project, the whole code will work through them. Unfortunately, not all libraries have an interface for dealing with promises, so they operate the old-fashioned way, through callbacks. In such cases, we need to wrap functions or promise them.

To create promises, use the Promise builder:

import fs from 'fs';

const promise = new Promise((resolve, reject) => {
  fs.readFile('/etc/passwd', (err, data) => {
    if (err) {
      reject(err);
      return;
    }
    resolve(data);
  });
});

Promises expect that a function will be called when it is created. Within that function, we should execute the asynchronous operation on the callback, that we want to turn into a promise.

Promises throw two callbacks into this function:

  • resolve - should be called if the asynchronous operation is completed successfully. As an input, it takes the result of the operation
  • reject - should be called in case of an error. As an input, it takes the error

These functions take exactly one argument, passed:

  • To then, if there is some data
  • Tocatch, if there is an error

It's enough to call at least one of these functions. It may be necessary to create a promise that always ends successfully, and you can easily do this without ever calling reject.

Finally, the new new Promise() construct returns a real promise we can work with the way we're used to:

promise
  .then(console.log)
  .catch(console.log)

What if we have to wrap two asynchronous operations or more? We should wrap each of them independently.

In other words, one asynchronous operation means one constructor new Promise. By the way, you can automate this task. There's a function built into node.js that makes promises out of asynchronous functions:

import util from 'util';
import fs from 'fs';

const stat = util.promisify(fs.stat);
stat('.').then((stats) => {
  // Doing something with `stats`
}).catch((error) => {
  // Handling the error
});

You can also do this in the front end — just search for a package with the promisify function.

In real life, some tasks have no asynchronous code, but they still need promises to build a chain.

You can make that promise yourself:

const promise = new Promise((resolve) => resolve());
// promise.then ...

It's the same for a promise that ends unsuccessfully:

const promise = new Promise((resolve, reject) => reject());
// promise.catch ...

For these tasks, there are abbreviations that make the code look cleaner:

const promise1 = Promise.resolve();
// promise1.then

const promise2 = Promise.reject();
// promise2.catch ...

Device

Promise-states

Technically, a promise is an object that has three states:

  • Pending
  • Fulfilled
  • Rejected

A promise starts in the pending state. Then, it uses functions resolve and reject, and translated into one of the final states — fulfilled or rejected. In these states, the promise cannot be rolled back or changed to another terminal state.

In other words, after calling resolve there's no way to make the promise have the rejected state by calling the reject function.

Recommended programs