Kapil gopinath
Kapil gopinath

Reputation: 1053

How to determine that all the files have been read and resolve a promise

The following code is responsible for reading files. My requirement is how to find whether all files has been read so that I can return or resolve a promise from the parent function(readmultifiles).

        $.when(readmultifiles(files))
               .then(function(){//all files uploaded}))

Above code initiates the file read. What can be done so that upon reading of all files callback is done or a return can be made.

        function readmultifiles(files) {               
            // Read first file
            setup_reader(files, 0);
        }


        function setup_reader(files, i) {
            var file = files[i];
            var name = file.name;
            var reader = new FileReader();
            reader.onload = function(e) {
                readerLoaded(e, files, i, name);
            };
            reader.readAsBinaryString(file);
            // After reading, read the next file.
        }

        function readerLoaded(e, files, i, name) {
            // get file content  
            var bin = e.target.result;
            // do sth with text


            // If there's a file left to load
            if (i < files.length - 1) {
                // Load the next file
                setup_reader(files, i + 1);
            }
        }

Upvotes: 1

Views: 1366

Answers (2)

jfriend00
jfriend00

Reputation: 707258

There are several things to consider in a good design using promises that your implementation could learn from:

  1. Create a promise (called "promisify") from the lowest level async operation your have. Then, you can use promise features to control the logic flow and propagate errors and your code will be consistently implemented with promises. In this case, it means you should promisify readFile(). It also makes readFile() more useful elsewhere in your project or in future projects.
  2. Make sure you are always propagating errors properly. With async code when not using promises, it can be hard to properly get errors back to the original caller, particular if the async logic ends up complicated (with nested or sequences operations).
  3. Consider carefully whether your async operations must be sequences or whether they can run in parallel. If one operation does not depend upon another and you aren't likely to overload some service with multiple requests, then running things in parallel will often achieve a result quicker.
  4. Return promises from async functions so callers can know when things are done and can access async results.
  5. Don't create another promise around an existing promise unnecessarily (considered one of the promise anti-patterns).
  6. If using jQuery promises, try to stick to jQuery features that are compatible with the promise standard so you don't run into interoperability issues going forward or confuse future readers of your code who are more likely to know how standard promises work.

Given all that, here are five ways to implement your code -using standard promises, using jQuery promises and with your operation sequences or run in parallel and using Bluebird promises. In all cases, you get an array of results in order at the end.

Promisify readFile() using standard promises

First, let's "promisify" your readFile operation so you can then use promise logic to control things.

function readFile(file) {
    return new Promise(function(resolve, reject) {
        var reader = new FileReader();
        reader.onload = function(e) {
            resolve(e.target.result);
        };
        reader.onerror = reader.onabort = reject;
        reader.readAsBinaryString(file);
    });
}

With standard promises, all operation in parallel

To run all your file operations in parallel and return all the results in order and use standard promises, you can do this:

function readmultifiles(files) {
    return Promise.all(files.map(readFile));
}

// sample usage
readmultifiles(arrayOfFiles).then(function(results) {
    // all results in the results array here
});

With standard promises, all operations in sequence

To run all your files operations in sequence (which it does not look like you need to do here because all the operations are indepedent even though your original code was sequencing them) and return all the results in order and use standard promises, you can do this.

This, somewhat standard design pattern for sequencing uses .reduce() to sequence through the array and chain all the operations together so they are run one at a time down the sequence of the chain:

function readmultifiles(files) {
    var results = [];
    files.reduce(function(p, file) {
        return p.then(function() {
            return readFile(file).then(function(data) {
                // put this result into the results array
                results.push(data);
            });
        });
    }, Promise.resolve()).then(function() {
        // make final resolved value be the results array
        return results;
    });
}

// sample usage
readmultifiles(arrayOfFiles).then(function(results) {
    // all results in the results array here
});

And, here's how it would look using jQuery promises

Promisify readFile() using jQuery promises:

function readFile(file) {
    return new $.Deferred(function(def) {
        var reader = new FileReader();
        reader.onload = function() {
            def.resolve(e.target.result);
        };
        reader.onerror = reader.onabort = def.reject;
        reader.readAsBinaryString(file);
    }).promise();
}

Run in a parallel with jQuery:

function readmultifiles(files) {
    return $.when.apply($, files.map(readFile));
}

// sample usage
readmultifiles(arrayOfFiles).then(function() {
    var results = Array.prototype.slice(arguments);
    // all results in the results array here
});

And, to run in sequence with jQuery

function readmultifiles(files) {
    var results = [];
    files.reduce(function(p, file) {
        return p.then(function() {
            return readFile(file).then(function(data) {
                // put this result into the results array
                results.push(data);
            });
        });
    }, $.Deferred().resolve()).then(function() {
        // make final resolved value be the results array
        return results;
    });
}

// sample usage
readmultifiles(arrayOfFiles).then(function(results) {
    // all results in the results array here
});

Bluebird implementation

And, for completeness, I'll show you what it looks like using a little more advanced promise library like Bluebird that has additional capabilities that are useful here. The parallel code and the implementation of readFile() is the same as for standard promises, but for the sequential implementation, it could take advantage of some built-in Bluebird operations for sequencing async operation and it would just consist of:

function readmultifiles(files) {
    return Promise.mapSeries(files, readFile);
}

// sample usage
readmultifiles(arrayOfFiles).then(function(results) {
    // all results in the results array here
});

Upvotes: 7

Kapil gopinath
Kapil gopinath

Reputation: 1053

What if I change the code structure to this

 $.when(readmultifiles(files)).then(
                        function(status) {
                            alert(status + ", things are going well");
                        },
                        function(status) {
                            alert(status + ", you fail this time");
                        },
                        function(status) {
                            $("body").append(status);
                        }
                );
       function readmultifiles(files) {

            var dfrd = $.Deferred();
            // Read first file
            setup_reader(files, 0);

            function setup_reader(files, i) {
                var file = files[i];
                var name = file.name;
                var reader = new FileReader();
                reader.onload = function(e) {
                    readerLoaded(e, files, i, name);
                };
                reader.readAsBinaryString(file);
                // After reading, read the next file.
            }

            function readerLoaded(e, files, i, name) {
                // get file content  
                var bin = e.target.result;
                // do sth with text

                namee.push(name);
                // If there's a file left to load
                if (i < files.length - 1) {
                    // Load the next file
                    setup_reader(files, i + 1);
                } else {
                    dfrd.resolve(namee.join(','));
                }
            }
            return dfrd.promise();
      }

Upvotes: 0

Related Questions