WebCake

This is the second of a two-part series. You can find the first post here.


I’ve previously written about creating Promise-based calls in Node scripts, and at the end I promised (BOOM!) to go into scaling the promise pattern to run multiple async calls. This post starts off where the other one ended, with the file looking something like this:

'use strict';

const
    fs = require('fs'),
    process = require('process');

writeFile('cake.txt', 'The cake is a lie!').then((successMsg) => {
    console.log(successMsg);
}, (errMsg) => {
    console.log(errMsg);
});

function writeFile(fileName, fileContent) {
    return new Promise((resolve, reject) => {
        fs.writeFile(fileName, fileContent, err => {
            if(err) {
                reject('We all had stuff to do, but I was given an exception');
            } else {
                resolve('Mission accomplished');
            }
        })
    });
}

If you’re not sure about what’s going on here, I explain it all in detail in the first post. Might be a good idea to go back and look it over.

This is a good starting point for demonstrating how we can scale async calls in Node by firing off multiple promises, and then running a function once they’re all done. In order to do this, we’ll use the Promise.all() method.

Background

So what’s the big deal here, you might ask? Since Node uses and encourages running asynchronous functions to compensate for being single-threaded, we as developers have to think carefully about the way in which we construct our applications; not only is this good for performance, it’s also good for readability.

This is where promises shine over async callbacks. With async callbacks, we can easily get lost in what’s fondly known as callback hell – the rabbit hole we find ourselves in when we constantly nest callbacks within callbacks, until we get the intended result.

Running a series of async functions and then doing more work once they’re done is a common callback-hell scenario. The example that we’ll be looking at today is one we’re we’ll take user input at the command line and create as many files as the user cares to list. This will build onto the functionality from the last post, where we created a file with a hard-coded name by allowing the user to pass the names of the files to the command, without limiting the user to one at a time.

Adding the Process

We currently only have one moule being pulled in with a require statement. That’s gotta change, since we’re also going to be working with the Process module.

The process module gives us access to information from and API access to the currently running process, including the bits that we’ll use in this demo, the argument vector. Using the argument vecror, we can see what was passed to the command line, and use that information to build out some functionality.

So let’s pull that in, right after we pull in the fs module:

const
    fs = require('fs'),
    args = require('process').argv;

Building the Promise Array

Next we’ll build an empty array, which we’ll add our promises to as we loop through the filenames passed to the argument vector:

let promiseArray = [];

The argument vector includes the command line executable command (in this case node), as well as the file name that we want Node to run, app.js. So when we loop through the argument vector, we have to skip over those two items to get to the file names.

for(let i = 2; i < args.length; i++) {
    // we'll add promises to the array here...
}

Now we’ll loop through each of the arguments passed after the file name; each time through we’ll add the writeFile() function call to the promiseArray, so that once we’ve built our array we can run them all at once:

for(let i = 2; i < args.length; i++) {
    promiseArray.push(
        writeFile(`${args[i]}.txt`, `The ${args[i]} is a lie!`).then(() => {
            return args[i];
        }, (errMsg) => {
            return console.log(errMsg);
        })
    );
}

As opposed to the original script that ran a hard-coded file name and hard-coded text through the fs.writeFile() method, here I’m taking whatever element from the process.argv is currently active in the loop, and passing it to both the file name, and the file content.

I’ve also changed the promise resolution methods to either return the active array element on success, or return a console.log() on failure. That’s important and I’ll get to why in a minute.

At this point, once we run our script, we’ll have an array of promise-based functions ready to be called. So let’s go ahead and add the functionality to call them.

Calling All Promises

Once we pass the promiseArray to the Promise.all() method, it will call each of the promise-based functions, and build an array of return data. That return data array is based on the order in which the promises finish, not the order in which they are called, mind you. That array will be passed to the resolution method on the Promise.all(), which will fire once all promises in the array have returned successfully.

If any of the promises fails, the Promise.all() method will also fail. That failing promise’s error handler will be passed to the error handled of the Promise.all(), and can be used accordingly.

Promise.all(promiseArray).then(successes => {
    console.log('All files created: ' + successes.join(', '));
}, err => {
    console.log(err);
});

Now if we run this file through Node with some file names after, we’ll see some files added to our directory, just like we did in the first demo.

$ node app.js cake bird potato

If you successfully run the command above, you should see the three corresponding files in your directory, and your console output should be something similar to All files created: cake, bird, potato.

The final code should look something like this:

'use strict';

const
    fs = require('fs'),
    args = require('process').argv;

let promiseArray = [];

for(let i = 2; i < args.length; i++) {
    promiseArray.push(
        writeFile(`${args[i]}.txt`, `The ${args[i]} is a lie!`).then(() => {
            return args[i];
        }, (errMsg) => {
            return console.log(errMsg);
        })
    );
}

Promise.all(promiseArray).then(successes => {
    console.log('All files created: ' + successes.join(', '));
}, err => {
    console.log(err);
});

function writeFile(fileName, fileContent) {
    return new Promise((resolve, reject) => {
        fs.writeFile(fileName, fileContent, err => {
            if(err) {
                reject('We all had stuff to do, but I was given an exception');
            } else {
                resolve();
            }
        })
    });
}

One response to “Calling All Promises – More ES6 Promises in Node”

  1. […] This is the first of a two-part series. You can find the second post here. […]

Leave a Reply

Your email address will not be published. Required fields are marked *