Search code examples
javascriptarrayscallbackpromisees6-promise

Combine result of promises into a single array in JavaScript


I try to parse all the files that were uploaded as a directory upload into a single array of files. I have a problem that I cannot combine result of all the promises into a single array. I get a multidimensional array instead.

function iterateThroughUploadedFiles(files, isEntry = false) {
    var promises = [];
    for (let i = 0; i < files.length; i++) {
        let entry = isEntry ? files[i].webkitGetAsEntry() : files[i];
        if (entry.isFile) {
            promises.push(new Promise(function(resolve, reject) {
                getFile(entry, resolve, reject);
            }));
        }
        else if (entry.isDirectory) {
            let dirReader = entry.createReader();

            var promise = new Promise(function(resolve, reject) {
                readEntries(dirReader, resolve, reject);
            });

            promises = promises.concat(promise);
        }
    }
    return Promise.all(promises);
}

function getFile(fileEntry, resolve, reject) {
    fileEntry.file(function(file) {
        resolve(file)
    });
}

function readEntries(dirReader, resolve, reject) {
    dirReader.readEntries(function (entries) {
        resolve(iterateThroughEntries(entries).then(function(result) {
            return result;
        }));
    });
}

Usage:

iterateThroughUploadedFiles(files, true).then(function(result) {
    console.log(result);
});

When I execute iterateThroughUploadedFiles function the result variable is a multidimensional array. However, I want this array to be flattened (example: [File(6148), Array(1), Array(0), File(14)]). I am not very familiar with callbacks and Promises...therefore, I have some issues working with them.

Edit:
There is a typo I made inside readEntries(dirReader, resolve, reject) function. There should be iterateThroughUploadedFiles instead of iterateThroughEntries function.


Solution

  • return Promise.all(promises);
    

    That resolves to a two dimensional array, which you can easily flatten by using some generator functions:

    function* flatten(arr) {
      for(const el of arr) {
        if(Array.isArray(el)) {
          yield* flatten(el);
        } else {
          yield el;
        }
      }
    }
    
    return Promise.all(promises).then(array => ([...flatten(array)]));
    

    While passing resolve and reject works kind of, that can be done way more elegant with just returning a new Promise from the getFile function:

    function getFile(fileEntry, resolve, reject) {
       return new Promise(resolve => fileEntry.file(resolve));
    }
    

    Then it can be easily chained

    function readEntries(dirReader) {
      return new Promise(resolve => dirReader.readEntries(resolve))
        .then(iterateThroughEntries);
    }
    
    function iterateThroughUploadedFiles(files, isEntry = false) {
       return Promise.all( files.map( file => {
         let entry = isEntry ? file.webkitGetAsEntry() : file;
         if (entry.isFile) {
           return getFile(entry);
         } else if (entry.isDirectory) {
           return readEntries( entry.createReader());
         }
       })).then(array => ([...flatten(array)]));
    }