Neues Node.js-Buch
Alle Artikel

Create a generic function to promisify asynchronous functions

Problem

You want to create a generic function to promisify asynchronous functions.

Ingredients

  • closures
  • rest parameters
  • spread operator
  • promises
  • arrow functions

Directions

  1. Given: an asynchronous function …

    const add = (x, y, z, callback) => {
      setTimeout(() => {
        callback(    // Remember:
          null,      // First argument: the error
          x + y + z  // Second argument: the result
        );
      }, 5000);
    }
  2. … that we call like this …

    add(2,3,4, (error, result) => {
      if(error) {
        console.error(error);
      } else {
        console.log(result));
      }
    }
  3. … but what we really want is this:

    const promisifiedAdd = promisify(add);
    promisifiedAdd(2,3,4)
      .then(result => console.log(result))
      .catch(error => console.error(error));
  4. So let’s get started! First create a function promisify() that takes a function …

    function promisify(fn) {
      ...
    }
  5. … and returns another function (that uses rest parameters).

    function promisify(fn) {
      return function (...args) {
        ...
      }
    }
  6. Let this function return a Promise object.

    function promisify(fn) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          ...
        });
      }
    }
  7. And inside the callback of the Promise object, call the original function, passing the rest parameters to it (using the spread operator) and a callback function.

    function promisify(fn) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          fn(...args, (error, result) => {
            ...
          });
        });
      }
    }
  8. Inside the callback function of the Promise object check if there is an error. If so, then call the method reject() passing the object object as argument.

    function promisify(fn) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          fn(...args, (error, result) => {
            if(error) {
              return reject(error);
            }
          });
        });
      }
    }
  9. Otherwise call the method resolve() passing the result object as argument.

    function promisify(fn) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          fn(...args, (error, result) => {
            if(error) {
              return reject(error);
            } else {
              return resolve(result);
            }
          });
        });
      }
    }
  10. Optimization: combine the last two steps.

    function promisify(fn) {
      return function (...args) {
        return new Promise((resolve, reject) => {
          fn(...args, (error, result) => {
            return error ? reject(error) : resolve(result);
          });
        });
      }
    }
  11. Optimization: replace function-style functions by arrow functions.

    const promisify = (fn) => {
      return (...args) => {
        return new Promise((resolve, reject) => {
          fn(...args, (error, result) => {
            return error ? reject(error) : resolve(result);
          });
        });
      }
    }
  12. Optimization: replace the return keywords and the brackets where possible.

    const promisify = fn =>                         // take the original function
      (...args) =>                                  // return another function
      new Promise((resolve, reject) =>              // that returns a promise
      fn(...args, (error, result) =>                // which calls the original
      error ? reject(error) : resolve(result)));    // and handles the results
  13. Voilá, a perfect generic function for converting asynchronous functions to promises.

    const promisify = fn =>
      (...args) =>
      new Promise((resolve, reject) =>
      fn(...args, (error, result) =>
      error ? reject(error) : resolve(result)));
    
    const add = (x, y, z, callback) => {
      setTimeout(() => {
        callback(null, x + y + z);
      }, 5000);
    }
    
    const promisifiedAdd = promisify(add);
    promisifiedAdd(2,3,4)
      .then(result => console.log(result))
      .catch(error => console.error(error));

Notes

  • Only works for functions where the last parameter is the callback.
  • Doesn’t check if the passed argument is a function.
  • Doesn’t check if the passed argument is a promise.
  • In the shown variant it only works if the promisified function does not need access to the execution context via this.

Alternative recipes

  • Use a promisify library.
  • Directly write all your code using promises.