The blog of dlaa.me
Archive: July, 2015
  • Not romantically binding [promise-ring wraps Node.js callbacks with native ES6 Promises]
    Monday, July 20th 2015

    JavaScript Promises are a powerful way of working with asynchronous code. They make sequencing operations easy and offer a clear, predictable way to handle errors that might occur along the way. Much has been written about the benefits of Promises and I won't try to repeat it here.

    What I do hope to do is make Promises a slightly more natural part of the Node.js development experience. In version 0.12.* (as well as in io.js), ES6 Promises are natively available. But the standard set of modules (such as File System) still use their original callback-based design and there's a bit of a disconnect between how you might want to write something and how you're able to. Fortunately, most of the Promise libraries that are already available include wrappers to convert callback-based functions into ones that return a Promise. However, most of those libraries assume you'll be using their custom implementation of Promise (from the "olden days" when that was the only option). And while different Promises/A+ implementations are meant to be interoperable, it seems silly to pull in a second Promise implementation when a perfectly good one is already available.

    That's where promise-ring comes in: it's a tiny npm package that provides functions to convert typical callback-based APIs into their Promise-based counterparts using the V8 JavaScript engine's native Promise implementation. Briefly:

    promise-ring is small, simple library with no dependencies that eases the use of native JavaScript Promises in projects without a Promise library.

    Documentation is available in the README along with runnable samples demonstrating the use of each API. It's all quite simple and exactly what you'd expect. A bonus feature is the wrapAll function which makes it easier to work with modules that expose many different callback-based functions (such as the File System module; see below).

    For an example of using promise-ring and Promises to simplify code, here is a typical callback-based snippet to copy a file onto itself:

    var fs = require("fs");
    
    // Copy a file onto itself using callbacks
    fs.stat(file, function(err) {
      if (err) {
        console.error(err);
      } else {
        fs.readFile(file, encoding, function(errr, content) {
          if (errr) {
            console.error(errr);
          } else {
            fs.writeFile(file, content, encoding, function(errrr) {
              if (errrr) {
                console.error(errrr);
              } else {
                console.log("Copied " + file);
              }
            });
          }
        });
      }
    });
    

    And here's the same code converted to use Promises via promise-ring:

    var pr = require("promise-ring");
    var fsp = pr.wrapAll(require("fs"));
    
    // Copy a file onto itself using Promises
    fsp.stat(file)
      .then(function() {
        return fsp.readFile(file, encoding);
      })
      .then(function(content) {
        return fsp.writeFile(file, content, encoding);
      })
      .then(function() {
        console.log("Copied " + file);
      })
      .catch(console.error);
    

    The second implementation is more concise, easier to follow, and DRY-er. That's the power of Promises! :)

    Find out more by visiting promise-ring on GitHub or promise-ring in the npm gallery.

    Tags: Node.js Technical