Converting JavaScript Callbacks to Promises

by Michael Szul on

No ads, no tracking, and no data collection. Enjoy this article? Buy us a ☕.

I use TypeScript for most anything nowadays. It's basically like programming in a less verbose C# or Java with a faster to prototype end result. One of the things that helps streamline code is the async/await functionality of TypeScript, which was folded into modern JavaScript. With async/await, instead of a callback:

function doStuff(key, callback) {
          //my code
          callback();
      }
      

…which forces you to map out the flow of your application in a series of function calls within function calls, you can rely on JavaScript Promises.

A Promise is JavaScript's way of waiting for an asynchronous function to return. It is a built-in method with a then() and a catch() function that allows you to handle responses and exceptions:

function doStuff(key) {
          return new Promise((resolve, reject) => {
              try {
                  const MY_RETURN_STUFF = //my code
                  resolve(MY_RETURN_STUFF);
              }
              catch(e) {
                  reject(e);
              }
          });
      }
      

This function returns a Promise. The Promise accepts an anonymous function that passing resolve() and reject() callbacks. When your code is complete, you call resolve(), and pass it what you want to return. When your code fails, you pass the error message to reject().

You can call this function with:

doStuff(key)
        .then((r) => {
          //do something with the result
      })
        .catch((e) => {
          //do something with the error
      });
      

In this code, you could do the callback from the original code inside of the then().

This becomes even simpler with async/await:

try {
          const result = await doStuff(key);
          //do something with the result
      }
      catch(e) {
          //do something with the error
      }
      

You can see that with a function that returns a Promise, you can simply await that function, and instead of using a then() function, it just returns the result.

This is great, but sometimes there are libraries that are constructed in a event-driven way, where callbacks are necessary, but they don't return Promises, so you can't use async/await. This makes your code look clunky. How you can solve for it?

Luckily, there are a few packages that you can use to fix this by wrapping the object/events/functions in a Promise.

One of the most popular is probably es6-promisify:

npm install es6-promisify
      

Once you have the package installed, you can wrap non-Promise code in a Promise pretty easily:

import * as azure from "azure-storage";
      import { promisify } from "es6-promisify";
      
      const tableService = azure.createTableService(process.env.AZURE_STORAGE_ACCOUNT, process.env.AZURE_STORAGE_ACCESS_KEY);
      
      const createTableIfNotExistsAsync = promisify(ats.createTableIfNotExists);
      
      await createTableIfNotExistsAsync("testTable");
      

In the above code (written in TypeScript), we wrap the createTableIfNotExists() method from the "azure-storage" library in a Promise, further cleaning up our code.