Andrew Wrestler
Andrew Wrestler

Reputation: 33

Handling multiple returns asynchronously in node.js

I chose node.js for my task because it have official Google API client library and is natural to work with JSON. But I'm struggling with asynchronous idioma and the guides I found on the internet doesn't cover my case.

So, in this example I'm trying to read the contents of two files and pass them to a method:

var fs = require('fs');

// Some more unrelated vars here

function readFiles(callback) {
        fs.readFile('./refresh_token', 'utf8', function(err,refreshToken) {
            if (err) throw err;
            callback(refreshToken);
        });

        fs.readFile('./access_token', 'utf8', function(err,accessToken) {
            if (err) throw err;
            callback(accessToken);
        });
};

function handleResults(refreshToken, accessToken) {
    oauth2Client.setCredentials({
        refresh_token: refreshToken,
        access_token: accessToken
    });
    proceedNext(oauth2Client);
};

function proceedNext(credentialsObject) {
    // do some more job
};

readFiles(handleResults);

Obviously it's not working because I'm doing two callbacks on the first position. But, what is the correct way (node.js way) to perform two methods asynchronously, pass the both results to the handling method, and then proceed further only after this been completed?

I tried something like this:

function readFiles() {
        fs.readFile('./refresh_token', 'utf8', function(err,refreshToken) {
            if (err) throw err;

            fs.readFile('./access_token', 'utf8', function(err,accessToken) {
                if (err) throw err;

                oauth2Client.setCredentials({
                    refresh_token: refreshToken,
                    access_token: accessToken
                });

                proceedNext();
            });
        });
};

And it worked after playing with global variables a bit, but I think this is a terrible, terrible idea and it's undermines the point of node.js. I'm looking for a correct way to do this, and I feel after passing this wall everything else should start making sense in node.js

Upvotes: 3

Views: 68

Answers (2)

Sigma
Sigma

Reputation: 542

If you prefer to use callbacks, try the async library: https://github.com/caolan/async

Especially async.parallel :

const async = require('async');

async.parallel({
    refreshToken: function(callback) { 
        fs.readFile('./refresh_token', 'utf8', function(err,refreshToken) {
            if (err) throw err;
            callback(null, refreshToken);
        });
    },
    accessToken: function(callback) { 
        fs.readFile('./access_token', 'utf8', function(err,accessToken) {
            if (err) throw err;
            callback(null, accessToken);
        });
    }
}, function(err, results) {
     // results is now equals to: {refreshToken: "...", accessToken: "..."}
});

Check here for more information: https://caolan.github.io/async/docs.html#parallel

Upvotes: 1

Josh Beam
Josh Beam

Reputation: 19802

At a high-level, you can kick off 2 promises in parallel and resolve only when they've both resolved:

Promise.all([
  new Promise((resolve, reject) => { /* your first fs read */ }),
  new Promise((resolve, reject) => { /* your second fs read */ })
]).then(result => {
  const refreshToken = result[0]
  const accessToken = result[1]

  // do more stuff
})
.catch(err => /* handle error */)

You'll need to look at the syntax for promises in MDN ES6 Promise.

To "promisify" one of your reads, it'd look something like:

new Promise((resolve, reject) => {
  fs.readFile('./refresh_token', 'utf8', function(err, refreshToken) {
    if (err) {
      reject(err)
    }
    resolve(refreshToken)
  })
})

To make life even simpler, you could use something like bluebird, which provides APIs for automatically "promisifying" stuff, etc.

The docs even show this for fs:

var Promise = require('bluebird');
var fs = Promise.promisifyAll(require("fs"));

fs.readFileAsync("myfile.js", "utf8").then(function(contents) {
    console.log(contents);
}).catch(function(e) {
    console.error(e.stack);
});

Upvotes: 5

Related Questions