3

How can I iterate through an array of data using Promises and returning data? I have seen some promises.push(asyncFunc) methods but some of the entries from my array will fail so from what I gather I can't use that.

var filesFromDisk = [
    '41679_4_2015-09-06_17-02-12.mp4',
    '41679_4_2015-09-06_17-02-12.smil',
    '41680_4_2015-09-09_10-44-05.mp4'
];

start(filesFromDisk)
    .then((data) => {
        console.log(data); // Want my data here
});

I start start(dbFiles) from another file which is why I want the data returned there.

function start(dbFiles) {
    var listOfFiles = [],
        promises = [];
    return new Promise((fulfill, reject) => {
        for (var i = 0; i < dbFiles.length; i++) { 
            getMp4(dbFiles[i])
                .then((data) => {
                    listOfFiles = listOfFiles.concat(data);
                    console.log(listOfFiles);
                })
        }
        fulfill(listOfFiles) // Need to happen AFTER for loop has filled listOfFiles
    });
}

So for every entry in my array I want to check if the file with the new extension exists and read that file. If the file with extension does not exist I fulfill the original file. My Promise.all chain works and all the data is returned in for loop above (getMp4(dbFiles[i]))

function getMp4(filename) {
    var mp4Files = [];
    var smil = privateMethods.setSmileExt(localData.devPath + filename.toString());
    return new Promise((fulfill, reject) => {
        Promise.all([
            privateMethods.fileExists(smil),
            privateMethods.readTest(smil)
        ]).then(() => {
            readFile(filename).then((files) => {
                fulfill(files)
            });
        }).catch((err) => {
            if (!err.exists) fulfill([filename]);
        });
    });
}

function readFile(filename){
    var filesFromSmil = [];
    return new Promise((fulfill, reject) => {
        fs.readFile(localData.devPath + filename, function (err, res){
            if (err) {
                reject(err);
            }
            else {
                xmlParser(res.toString(),  {trim: true}, (err, result) => {
                    var entry = JSON.parse(JSON.stringify(result.smil.body[0].switch[0].video));
                    for (var i = 0; i < entry.length; i++) { 
                        filesFromSmil.push(privateMethods.getFileName(entry[i].$.src))
                    }
                });
                fulfill(filesFromSmil);
            }
        });
    });
};

Methods in the Promise.all chain in getMp4 - have no problems with these that I know.

var privateMethods = {
    getFileName: (str) => {
        var rx = /[a-zA-Z-1\--9-_]*.mp4/g;
        var file = rx.exec(str);   
        return file[0];
    },
    setSmileExt: (videoFile) => {
        return videoFile.split('.').shift() + '.smil';
    },
    fileExists: (file) => {
        return new Promise((fulfill, reject) => {
            try {
                fs.accessSync(file);
                fulfill({exists: true})
            } catch (ex) {
                reject({exists: false})
            }
        })
    },
    readTest: (file) => {
        return new Promise((fulfill, reject) => {
            fs.readFile(file, (err, res) => {
                if (err) reject(err);
                else fulfill(res.toString());
            })
        })
    }
}
Thomas Johansen
  • 14,837
  • 3
  • 14
  • 21

1 Answers1

7

If you need them to run in parallel, Promise.all is what you want:

function start(dbFiles) {
    return Promise.all(dbFiles.map(getMp4));
}

That starts the getMp4 operation for all of the files and waits until they all complete, then resolves with an array of the results. (getMp4 will receive multiple arguments — the value, its index, and a a reference to the dbFiles arary — but since it only uses the first, that's fine.)

Usage:

start(filesFromDisk).then(function(results) {
    // `results` is an array of the results, in order
});

Just for completeness, if you needed them to run sequentially, you could use the reduce pattern:

function start(dbFiles) {
    return dbFiles.reduce(function(p, file) {
        return p.then(function(results) {
            return getMp4(file).then(function(data) {
                results.push(data);
                return results;
            });
        });
    }, Promise.resolve([]));
}

Same usage. Note how we start with a promise resolved with [], then queue up a bunch of then handlers, each of which receives the array, does the getMp4 call, and when it gets the result pushes the result on the array and returns it; the final resolution value is the filled array.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @Alnitak: **OMG**, did I really write `.map(function(file) { return getMp4(file); })`! Need more coffee. Thank you. (Fixed.) – T.J. Crowder Jan 12 '17 at 09:20
  • you're welcome :) Extraneous function wrappers always bug me... :p – Alnitak Jan 12 '17 at 09:21
  • @Alnitak: Me too...normally. :-) – T.J. Crowder Jan 12 '17 at 09:21
  • Ah, that made more sense. However, some files from the array will be catched and the `Promise.all` return nothing. If I have not completely misunderstood this. Bot snippets return me no result. I'm confused. – Thomas Johansen Jan 12 '17 at 09:29
  • @ThomasJohansenToft: I'm sorry, I don't know what you mean by "...some files from the array will be catched and the `Promise.all` return nothing..." – T.J. Crowder Jan 12 '17 at 09:31
  • 1
    @ThomasJohansenToft: If `getMp4` resolves the promise it returns, then that resolution value will be in the array. I'm afraid I can't help you debug what's going on in `getMp4`, but the above is definitely how you do a bunch of operations in parallel and get a final result with the results of all of them. – T.J. Crowder Jan 12 '17 at 09:40
  • @T.J.Crowder I'm interested to know if using a for..of, with an await inside the loop would cause any issues when reading sequentially? Something like https://stackoverflow.com/a/37576787/110495 - Sorry for digging up a 6 year old answer! – dan richardson Apr 14 '23 at 15:48
  • 1
    @danrichardson - No worries! No, there's nothing wrong with doing that, if you really want things done sequentially. – T.J. Crowder Apr 14 '23 at 16:41