Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promises.all() on an array of objects with a 'promised' property

Edit: In the mere hours since asking this question, I've learned enough to realise this question is a perfect case study in egregious abuse of promises:

I'm trying to convert a workflow to using promises because it was a horrible rat-nest of callbacks, and I was going well, until I got to a particular function. I'm trying to use the promisified version of readFile() to get the contents of a series of files, but I also need to hold on to the filename of each file so it can be added into the object that will eventually be parsed from the file contents in the next step/function. This filename is only accessible while I'm iterating over the list of filenames (and creating promises to return the contents of the file). Right now I have this, which won't work:

    // Accepts a sequence of filenames and returns a sequence of objects containing article contents and their filenames
    function getAllFiles(filenames) {
      var files = []; // This will be an array of objects containing Promises on which we want to call .all()
      filenames.each( function (filename) {
        files.push({content: fsPromise.readFileAsync(articlesPath + '/' + filename, 'utf8'), 
                   filename: filename});    
      });

This is an attempt to express what I want to do next:

      Promise.all(files).then( function(files) {
        return Lazy(files);
      });
    } 

In other words, what I want to get out of the array of promises is an array of objects like {content: "FILE CONTENTS", filename: "fileN.txt"}. The first part is the result of the readFileAsync(), so it's a promise that needs to resolve before I can move on to the next step. As far as I can tell, that filenames.each() call is my only chance at associating file contents with the name of the file it came from. I can't control the contents of the files, so I can't store the filenames inside the file contents, and that would be ugly data redundancy and a bad idea anyway. The Lazy() call is just to transform the finished array into a lazy.js Sequence, as that's the library I'm using for collection handling in the rest of the project.

Half-solution I'm considering

Would it be possible to extract the promise parts out of the objects and store them in a seperate array, then call .all() on that? In other words, when all the promises are done, I go back into the original array and retrieve the matching filename by index? I feel like that would work, if I'm able to populate the second array with references to the original promises (i.e. so I know when the originals have resolved). Would this work?

Alternatively,

Could I just go synchronous inside that .each() call? E.g.

filenames.each( function(filename) {
  files.push({content: fsPromise.readFileAsync(articlesPath + '/' + filename, 'utf8')
              .then( function(fulfilledContent) {return fulfilledContent}), 
              filename: filename
});

(I know I also need to handle the possibility of these promises being rejected instead of resolved – I'll work out how to do that once I've worked out if what I'm trying to do is even possible)

like image 726
Toadfish Avatar asked Feb 17 '16 15:02

Toadfish


People also ask

What is the use of Promise all ()?

The Promise.all() method takes an iterable of promises as input and returns a single Promise . This returned promise fulfills when all of the input's promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values.

What is Promise resolve () then?

The Promise.resolve() method "resolves" a given value to a Promise . If the value is a promise, that promise is returned; if the value is a thenable, Promise.resolve() will call the then() method with two callbacks it prepared; otherwise the returned promise will be fulfilled with the value.

Which of the Promise methods resolves only when all of the given promises get resolved or else get rejected when any of them fails?

The Promise.allSettled([promises]) - This method waits for all promises to settle(resolve/reject) and returns their results as an array of objects. The results will contain a state (fulfilled/rejected) and value, if fulfilled. In case of rejected status, it will return a reason for the error.

Does Promise all run promises in parallel?

As you can see, Promise. all executes code concurrently, but what is parallel execution? JavaScript is single-threaded and can only perform a single chunk of work at a time, so parallel execution is not possible with JavaScript, except for some circumstances such as web workers.


1 Answers

There are two simple solutions:

  • Access the filenames array in the last callback. It's in the same order as the array of contents that Promise.all will fulfill with:

    Promise.all(filenames.map(function(filename) {
        return fsPromise.readFileAsync(articlesPath + '/' + filename, 'utf8');
    })).then(function(contents) {
        return Lazy(contents).map(function(content, i) {
            return {content: content, filename: filenames[i]};
        });
    })
    
  • Make promises for the object with both filename and content immediately when iterating the array:

    Promise.all(filenames.map(function(filename) {
        return fsPromise.readFileAsync(articlesPath + '/' + filename, 'utf8').then(function(content) {
            return {content: content, filename: filename};
        });
    })).then(Lazy);
    
like image 51
Bergi Avatar answered Sep 28 '22 06:09

Bergi