Using Promises with fs.readFile in a loop
So, anytime you have multiple async operations to coordinate in some way, I immediately want to go to promises. And, the best way to use promises to coordinate a number of async operations is to make each async operation return a promise. The lowest level async operation you show is fs.readFile()
. Since I use the Bluebird promise library, it has a function for "promisifying" a whole module's worth of async functions.
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
This will create new parallel methods on the fs
object with an "Async" suffix that return promises instead of use straight callbacks. So, there will be an fs.readFileAsync()
that returns a promise. You can read more about Bluebird's promisification here.
So, now you can make a function that gets an image fairly simply and returns a promise whose value is the data from the image:
function getImage(index) {
var imgPath = __dirname + "/image1" + index + ".png";
return fs.readFileAsync(imgPath);
}
Then, in your code, it looks like you want to make bFunc()
be a function that reads three of these images and calls cFunc()
when they are done. You can do that like this:
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
function getImage(index) {
var imgPath = __dirname + "/image1" + index + ".png";
return fs.readFileAsync(imgPath);
}
function getAllImages() {
var promises = [];
// load all images in parallel
for (var i = 0; i <= 2; i++) {
promises.push(getImage(i));
}
// return promise that is resolved when all images are done loading
return Promise.all(promises);
}
getAllImages().then(function(imageArray) {
// you have an array of image data in imageArray
}, function(err) {
// an error occurred
});
If you did not want to use Bluebird, you could manually make a promise version of fs.readFile()
like this:
// make promise version of fs.readFile()
fs.readFileAsync = function(filename) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, data){
if (err)
reject(err);
else
resolve(data);
});
});
};
Or, in modern versions of node.js, you can use util.promisify()
to make a promisified version of a function that follows the node.js async calling convention:
const util = require('util');
fs.readFileAsync = util.promisify(fs.readFile);
Though, you will quickly find that once you start using promises, you want to use them for all async operations so you'll be "promisifying" lots of things and having a library or at least a generic function that will do that for you will save lots of time.
In even newer versions of node.js (version 10.0+), you can use the built-in version of the fs
library that supports promises:
const fsp = require('fs').promises;
fsp.readFile("someFile").then(data => {
console.log(data);
});
Node v10 has fs Promises API
const fsPromises = require('fs').promises
const func = async filenames => {
for(let fn of filenames) {
let data = await fsPromises.readFile(fn)
}
}
func(['file1','file2'])
.then(res => console.log('all read', res))
.catch(console.log)
https://nodejs.org/api/fs.html#fs_fs_promises_api
Or if you want to read more files simultaneously:
const func = filenames => {
return Promise.all(
filenames.map(f => fsPromises.readFile(f))
)
}
func(['./a','./b'])
.then(res => console.log('all read', res))
.catch(console.log)