How to Promisify Your Javascript

How to Promisify Your Javascript

Making synchronous JavaScript async with Promises

2021-12-25

If you’re a JavaScript developer, you’ve doubtless spent countless hours working with promises. And while most web devs have acquired at least a general familiarity with promises due to necessity, too many developers struggle to make asynchronous code work due to an incomplete understanding of how to use them.

Learning to work with promises is an admirable goal, but we’ll approach it from the other side, by teaching you to make your asynchronous code using promises.

Creating your own promises will develop your intuition around how and why promises are used, and make it much easier to work with asynchronous code written by others.

We’ll also differ from typical tutorials on promises by focusing entirely on realistic use cases. Knowledge is easier to apply in real-life scenarios when it’s taught via practical examples, after all.

What are promises?

Before ES6 came around, the only way to achieve asynchronicity was using methods built in to the JS engine itself. Originally, only setTimeout and setInterval were provided, and later Ajax methods.
So we could hack our way around setInterval to check whether another asynchronous operation had finished, by running some code every few milliseconds to check the status of an action.


const request = makeWebRequest('https://example.test/some_path');
const checkRequestStatus = () => {
	if (request.hasOwnProperty('status')) {
		console.log(`Request completed with status ${request.status}`)
	}
}
// 500 milliseconds = check twice per second
setInterval(checkRequestStatus, 500);

If we simply wished to run a task in the background and didn’t need to check the results, we could put it inside of a call to setTimeout with the wait time set to zero. This is equivalent to saying ‘Run this function asynchronously right now


const someFunc = () => ...actions to run asynchronously;
setTimeout(someFun, 0);

These hacks are good enough in a purely practical sense, but make it challenging to get the results of asynchronous operations reliably. Promises offer a more developer friendly approach to running code ‘in the background’. The makeWebRequest pseudocode from before would be refactored like so, using promises:


makeWebRequest('https://example.test/some_path')
  .then(request => {
	console.log(`Request completed with status ${request.status}`)
  });

Chaining together calls to .then allow us to run certain actions one after the other, while the rest of our program continues ahead. ES7 introduces additional syntax for getting the result of a promise, known as async/await. If you’re unfamiliar with this, and just want some great reading material on promises to prepare you for the rest of this article, consider perusing the following two resources:

But if you have a rough, working idea of promises, you should be fine to plow straight ahead. Let’s dive in!

Promisifying code

Asynchronicity shines when we’re waiting for some IO operation to finish when we’d prefer to continue doing other things in the meantime. Reading files is a typical example. So let’s write a simple utility that recursively shows you all of the files in a directory that contain a certain string.

First, let’s make the code synchronously. We’ll do our best to make the most minimalist example possible.


const searchFiles = (directory, string) => {
  if (statSync(directory).isDirectory()) {
    for (const file of readdirSync(directory)) {
      searchFiles(`${directory}/${file}`, string);
    }
  } else {
    if (readFileSync(directory).includes(string)) {
      console.log(directory);
    }
  }
}

According to the time command, this takes 104 milliseconds to run on my system. Now, using promises provided by Node, we can cut that time in half:


const searchFiles = (directory, string) => {
  stat(directory)
  .then(s => {
    if (s.isDirectory()) {
      readdir(directory)
      .then(files => {
        files.forEach(file => searchFiles(`${directory}/${file}`, string))
      })
    } else {
      readFile(directory)
      .then(data => {
        if (data.includes(string)) {
          console.log(directory)
        }
      })
    }
  });
}

The structure is a bit awkward because fs is a module from before promises became the default way to manage asynchronicity in JavaScript. But the idea is clear enough.

But working with premade promises is no fun, how do we get to the real stuff, making our own promises to handle tough situations? Well, imagine we had an Event Listener. This listener gives data to a callback once some condition is met. Consider this example, using the ssh2 library:


const conn = new Client();
conn.on('ready', () => {
  console.log('Client :: ready');
  conn.exec('uptime', (err, stream) => {
    if (err) throw err;
    stream.on('close', (code, signal) => {
      console.log('Stream :: close :: code: ' + code + ', signal: ' + signal);
      conn.end();
    }).on('data', (data) => {
      console.log('STDOUT: ' + data);
    }).stderr.on('data', (data) => {
      console.log('STDERR: ' + data);
    });
  });
}).connect(cfg);

Wonderful, but how do we get the result of the output and then do something with it? We could put it in an object, and then monitor that object with setInterval, but now we’re implementing the same naive hacks we were using back before promises existed.

The trick is to create a promise, wrap the functionality in it, and then resolve upon the data we actually want. Using the above example, that looks like this:


const p = new Promise(resolve => {
  const conn = new Client();
  conn.on('ready', () => {
    let output = '';
    conn.exec('ls -l', (err, stream) => {
      stream.on('close', () => {
        resolve(output);
        conn.end();
      }).on('data', (data) => {
        output += data;
      })
    });
  }).connect(cfg);
})

Viola, now we can easily access the output using the promise, as we do on the last line!

Armed with this technique alone, you can convert pretty much any arbitrary JS code into a promise. But what if you don’t want to, or can’t, wrap the listener code inside of a promise? Is there a way we can create the promise, and then pass it’s resolve callback into the code we want to promisify?
As it turns out, we can accomplish exactly this functionality using the Deferred Promise pattern.

A Deferred Promise just means that we create a promise and extract it’s resolve method to use it outside of the scope of the promise body. It’s easier to explain using an example, so to that end we’ll turn the SSH code above into a deferred pattern.


// Create a deferred object, which now
// contains the following properties:
// d.promise, d.resolve, and d.reject
const d = new Deferred();
d.promise.then(output => console.log(output));

const conn = new Client();
conn.on('ready', () => {
  let output = '';
  conn.exec('ls -l', (err, stream) => {
    stream.on('close', () => {
      // Resolve the promise in d.promise.
      // This will trigger d.promise.then above
      d.resolve(output);
      conn.end();
    }).on('data', (data) => {
      output += data;
    })
  });
}).connect(cfg);

Deferring promises like this is often called an antipattern. Most of the time it can be avoided and replaced with conventional promises. The above code is an example where we don’t gain much by leaning on deferred promises.
But sometimes it’s absolutely necessary, such as when you need to change the ‘current’ promise conditionally right after resolving.

To explore this deeper, read the seminal document on this topic - Promise anti-patterns by bluebird.

Cool promise tricks

Once you get the hand of promises, you’ll start using them everywhere. But JavaScript already includes some interesting methods for using promises in clever ways. Let’s look at some of the most popular examples!

What if you wanted to write a status page that verifies that all of your APIs are up? This can be accomplished easily via Promise.all()! It looks like this:


const axios = require('axios');

const urls = [
  'https://api1.test/vs/',
  'https://api2.test/vs/',
  'https://api3.test/vs/'
]

const requests = urls.map(url => axios.get(url));
Promises.all(requests)
  .then(results => {
    if (!results.every(result => result.status === 20)) {
      console.log(`Error code: ${result}`)
    } else {
      console.log('All APIs are up!')
    }
  }).catch(err => {
    console.log(`Connection to API failed: ${err}`);
  });

With just that simple snippet, have the base functionality to start building a pretty decent backend for an application that monitors uptime for services.

Even compared to a beginner friendly language like Python, promises allow us to easily make any code both asynchronous and easier to work with than other popular approaches to asynchronicity used by older languages. While promises aren’t the easiest thing to master, they are certainly less hard on beginners than mutexes and interprocess communication.

Simple enough!

What else can we do with promise methods? Okay, let’s say we want to poll a few APIs to see if we have any notifications.
There’s also a convenient promises API for that:


const axios = require('axios');

const requests = [
  'https://api1.test/vs/check_notification',
  'https://api2.test/vs/check_notification',
  'https://api3.test/vs/check_notification'
].map(url => axios.get(url));

Promises.any(requests).then(notification => console.log(notification.body));

There’s much more - for a thorough overview of useful promise methods, check out the excellent documentation from Mozilla on the topic (I especially recommend reading about Promise.race() and Promise.allSettled() if you want to see more useful promise methods!).

You may have also heard that asynchronicity is great for functional code. It’s true that code without so-called ‘side effects’ is easily turned into async code. We could demonstrate this using a theoretical, computer science example, like the Fibonnaci sequence.
But we want to focus on use cases that resemble programs you’ll encounter in the wild. So let’s write a super simple, recursive web crawler:


var request = require('sync-request');

const getLinks = (html) => html
  .match(/href=["']https:\/\/(.*?)['"]/g)
  .map(e => e.split(/['"]/)[1]);

const spider = (url, maxDepth=3, linksPerPage=20, alreadyFound=[]) => {
  try {
    if (!maxDepth) { throw Error('Max depth reached'); }
    const res = request('GET', url);
    const html = res.getBody().toString();
    for (const link of getLinks(html)) {
      if (!linksPerPage) { throw Error('Maximum links crawled'); }
      linksPerPage--;
      console.log(`Link found: ${link}`);
      alreadyFound.push(link);
      spider(link, maxDepth-1, linksPerPage, alreadyFound);
    }
  } catch {
  }
}

Wonderful. A bit lacking in terms of proper error handling, our regex for finding links is too primitive, and this primitive recursion could lead to a RecursionDepthExceeded error if we let it recur to deep. Still, not a bad start!

But wouldn’t it be nice if, instead of making a request, then another, then another, we could initiate dozens of requests? We’d still need to wait for the request before getting the HTML code to search for more links, but all the links on a single page could launch many simultaneous requests.


const axios = require('axios');

const getLinks = (html) => html
  .match(/href=["']https:\/\/(.*?)['"]/g)
  .map(e => e.split(/['"]/)[1]);

const spider = (url, maxDepth=3, linksPerPage=20, alreadyFound=[]) => {
  if (!maxDepth) { throw Error('Max depth reached'); }
  axios.get(url).then(result => {
    const html = result.data;
    for (const link of getLinks(html)) {
      if (!linksPerPage) { throw Error('Maximum links crawled'); }
      linksPerPage--;
      console.log(`Link found: ${link}`);
      alreadyFound.push(link);
      spider(link, maxDepth-1, linksPerPage, alreadyFound);
    }
  }).catch(e => {})
}

spider('https://news.ycombinator.com');

Try it out! You should quickly begin to see numerous links displayed on the terminal. Although the specific links will probably be different by the time you run it, here is the result of running this as of December 16th, 2021.

$ node syncSpider.js 

Link found: https://news.ycombinator.com
Link found: https://tqdm.github.io/
Link found: https://defrag.shiplift.dev/
Link found: https://there.oughta.be/a/wifi-game-boy-cartridge
Link found: https://www.npr.org/2021/12/16/1064628654/facebook-bans-surveillance-firms-that-spied-on-50000-people
Link found: https://www.pola.rs/
[and so on! If we were to show the full output, it would take up several pages!]

And shockingly, the promise based version results in less lines of code than the original, slower version! Since we’re making a lot of blocking IO requests, this kind of code is the perfect place to use promises


In conclusion, making normal code asynchronous is easy to do. But to avoid headaches, you have to build some relatively simple intuitions about how promises work and what kinds of problems they solve. Practice is key.
There’s plenty of great reading material that’s been produced by the web dev community that elaborately explains the different APIs and use cases, and that’s fine if you just want a theoretical understanding of what promises are all about. But our goal is to nudge you towards being able to create your own asynchronous code in any situation, and what will really solidify promises is using them to solve problems.

Even fake practice problems will work, so spend a good chunk of time just trying things out and experimenting. You’ll save so much time when you’re debugging promisified code that the few hours you invest will be insignificant in comparison.