0

Here is some a method that takes in an array of files (Files in HTML5 file API), and uses a promise to return the text in the file since reading is an async operation. The problem is that when I go to access the texts in a different method, it still contains promise objects that have been resolved, but I want to replace those promises with the actual text itself. The return statement gets called before the promise becomes resolved so I'm not entirely sure what to do.

function getTextFromJsonFiles(files) {
 var texts = [];
 var reader = new FileReader();

 for (let file of files) {
    var textPromise = new Promise(function (resolve, reject) {
        reader.readAsText(file);
        reader.onload = function (evt) {
            resolve(evt.target.result);

        };
    });


    texts.push(textPromise);
}

return texts;
 }
Kevin Zhou
  • 85
  • 1
  • 6
  • 1
    Just put a 'then' after it: `textPromise.then(function( value ){}` The value sent to the function will be the resolved value you can inteact with. – Shilly Oct 20 '16 at 15:45
  • 1
    @Shilly: `getTextFromJsonFiles` returns an array, not a promise – Felix Kling Oct 20 '16 at 15:45
  • Yes ofcourse, it just depends on where you want to put the then. He could even map the array with promises later. But the sensible thing is indeed to promise.all and continue the then chain outside the getText function as you show in your answer. – Shilly Oct 20 '16 at 16:02

2 Answers2

1

getTextFromJsonFiles should return a single promise that resolves to the values of the other promises. This can easily be achieved with Promise.all:

return Promise.all(texts);

Usage:

getTextFromJsonFiles(...).then(texts => {
  console.log(texts);
});

I also don't think you can reuse reader this way. If you are parallelizing file reading, you have to create a separate reader per file. Here is a corrected version of your function:

function getTextFromJsonFiles(files) {
  return Promise.all(files.map(file => new Promise(resolve => {
    var reader = new FileReader();
    reader.readAsText(file);
    reader.onload = evt => resolve(evt.target.result);
  })));
}
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • When the promises get resolved in Promise.all, would that return an array of resolved promises containing the texts or the texts themselves? – Kevin Zhou Oct 20 '16 at 15:59
  • The promise resolves to an array of texts. Have a look at the examples in the docs! – Felix Kling Oct 20 '16 at 16:41
1

You should use Promise.all if you want to return an array of asynchronous process. So your function should return a promise, and the calling function should resolve that.

function getTextFromJsonFiles(files) {
    var textPromises = [];
    var reader = new FileReader();

    for (let file of files) {
        var textPromise = new Promise(function (resolve, reject) {
            reader.readAsText(file);
            reader.onload = function (evt) {
                resolve(evt.target.result);
            };
            reader.onerror = reject; // if any error occurs promise should  reject the result
        });
        textPromises.push(textPromise);
    }

    return Promise.all(textPromises);
}

Then you can use getTextFromJsonFiles as following:

getTextFromJsonFiles(files).then(function(texts){
     //process texts here
}).catch(function(error){
     //if any one of the text promises fail, this function will be called, you can handle error here.
})

Also please check @felix-kling's answer for how to use FileReader.

cubbuk
  • 7,800
  • 4
  • 35
  • 62