Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Promises with fs.readFile in a loop

I'm trying to understand why the below promise setups don't work.

(Note: I already solved this issue with async.map. But I would like to learn why my attempts below didn't work.)

The correct behavior should be: bFunc should run as many time as necessary to fs read all the image files (bFunc below runs twice) and then cFunc console prints "End".

Thanks!

Attempt 1: It runs and stops at cFunc().

var fs = require('fs');  bFunc(0) .then(function(){ cFunc() }) //cFunc() doesn't run  function bFunc(i){     return new Promise(function(resolve,reject){          var imgPath = __dirname + "/image1" + i + ".png";          fs.readFile(imgPath, function(err, imagebuffer){              if (err) throw err;             console.log(i)              if (i<1) {                 i++;                 return bFunc(i);             } else {                 resolve();             };          });      }) }  function cFunc(){     console.log("End"); } 

Attempt 2: In this case, I used a for-loop but it executes out of order. Console prints: End, bFunc done, bFunc done

var fs = require('fs');  bFunc()         .then(function(){ cFunc() })  function bFunc(){     return new Promise(function(resolve,reject){          function read(filepath) {             fs.readFile(filepath, function(err, imagebuffer){                 if (err) throw err;                 console.log("bFunc done")             });         }          for (var i=0; i<2; i++){             var imgPath = __dirname + "/image1" + i + ".png";             read(imgPath);         };          resolve()     }); }   function cFunc(){     console.log("End"); } 

Thanks for the help in advance!

like image 524
David Avatar asked Jan 06 '16 08:01

David


People also ask

Does FS readFile return promise?

Return Value: It returns a Promise. The Promise is resolved with the contents of the file.

What is the difference between FS readFile and FS readFileSync?

In fs. readFile() method, we can read a file in a non-blocking asynchronous way, but in fs. readFileSync() method, we can read files in a synchronous way, i.e. we are telling node.

Does readFile return anything?

readFile. Returns the contents of the file named filename. If encoding is specified then this function returns a string. Otherwise it returns a buffer.

Is writeFile a promise?

writeFile() method is used to asynchronously write the specified data to a file. By default, the file would be replaced if it exists. The 'options' parameter can be used to modify the functionality of the method. The Promise will be resolved with no arguments upon success.


2 Answers

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); }); 
like image 181
jfriend00 Avatar answered Sep 28 '22 02:09

jfriend00


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) 
like image 22
Dmitry Yudakov Avatar answered Sep 28 '22 03:09

Dmitry Yudakov