0

!!! this question is not about how to pass an array of promises to the when function since neither file method nor onload return promises, its not a duplicate

I have a custom method that executes async functions which DO NOT return promises. My custom method does return a promise. What is the correct way to implement the functionality - using timeout like in example 1 or checking for equality in the callback function like in example 2?
EXAMPLE1:

getAllFiles: function () {
    var def = $.Deferred();
    var files = [];
    fileEntries.forEach(function(fileEntry){
        fileEntry.file(function (file) {
            var reader = new FileReader();
            reader.onload = function (e) {
                files.push({
                    name: fileEntry.fullPath,
                    content: e.target.result
                });
            };
            reader.readAsArrayBuffer(file);
        });
    });

    //this resolves the deffered    
    var interval = setInterval(function(){
       if (files.length === fileEntries.length) {
           def.resolve(files);
           clearInterval(interval);
       }
    }, 1000);

    return def.promise();
}

EXAMPLE 2

getAllFiles: function () {
    var def = $.Deferred();
    var files = [];
    fileEntries.forEach(function(fileEntry){
        fileEntry.file(function (file) {
            var reader = new FileReader();
            reader.onload = function (e) {
                files.push({
                    name: fileEntry.fullPath,
                    content: e.target.result
                });
                //this resolves the deffered
                if (files.length === fileEntries.length) {
                   def.resolve(files);
                   clearInterval(interval);
                }
            };
            reader.readAsArrayBuffer(file);
        });
    });

    return def.promise();
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • It looks like you're using jQuery, which means you'd be able to take advantage of [`$.when`](http://api.jquery.com/jQuery.when/). – zzzzBov Aug 20 '14 at 15:18
  • See http://stackoverflow.com/q/6538470/218196 – Felix Kling Aug 20 '14 at 15:20
  • @zzzzBov, I don't see how I can use jQuery's when here. Could you guys please provide an example specific to my situation? To user `when` async methods should return promises, but neither `file` method nor `onload` return promises. – Max Koretskyi Aug 20 '14 at 15:36
  • Your second example is incorrect because you resolve the deferred more than once (on every loop iteration, in fact). Depending on your requirements, a timer may not be reliable enough. Have you tried maintaining a counter, and only resolving the deferred when the last file has been read? – Frédéric Hamidi Aug 20 '14 at 16:29

1 Answers1

1

Simply make a promise for each async function (onload handler) then!

getAllFiles: function () {
    var deferreds = fileEntries.map(function(fileEntry){
        var def = $.Deferred();
        fileEntry.file(function (file) {
            var reader = new FileReader();
            reader.onload = function (e) {
                def.resolve({
                    name: fileEntry.fullPath,
                    content: e.target.result
                });
            };
            reader.onerror = def.reject; // don't forget error handling!
            reader.readAsArrayBuffer(file);
        }, def.reject); // here as well
        return def;
    });
    return $.when.apply($, deferreds).then(function() {
        var files = $.map(arguments, function(args) { return args[0]; });
        return files;
    });
}

(the mapping over the arguments in the then is necessary because jQuery's $.when result is so ugly)

To answer your actual question:

What is the correct way to implement the functionality - using timeout like in example 1 or checking for equality in the callback function like in example 2?

Do it from the callback. Polling results with setInterval is despised (it's slower, won't catch errors and even leak in the error case).

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for taking time to respond, it gave me new insight into Jquery's defferds :). One question though, why do you return `def` from `file` callback instead of `def.promise()`? – Max Koretskyi Aug 21 '14 at 10:15
  • You can do either. A `Deferred` implements the `Promise` interface that `when` uses, calling `.promise()` on it would just construct another object. – Bergi Aug 21 '14 at 10:22
  • Thanks. But then the `def` can be resolved outside for the `getAllFiles` function, right? If we return `Promise` we prevent it. I also don't see why I need this `var files = $.map(arguments, function(args) { return args[0]; });` since arguments already contains an array of objects.? I can simple turn it into real Array by `return Array.prototype.slice.call(arguments);` – Max Koretskyi Aug 21 '14 at 10:31
  • What is weird though is that when I call the method like that `getAllFiles().done(function (files) { });` the `files` variable contains only the first object instead of array returned by `then`. Any ideas why that happens? – Max Koretskyi Aug 21 '14 at 10:34
  • No, `def` cannot be resolved outside `getAllFiles` because we don't return the `def`, but the promise constructed through `$.when`. That's why it's save to use a deferred here. If you'd put reading a single file entry in a separate function, then there you should indeed use `.promise()`. It might be cleaner here as well, if not only for reducing confusion :-) – Bergi Aug 21 '14 at 10:45
  • The problem is that `arguments` in the `when` callback is not an array-like object of the objects, but an array-like object of argument arrays that contain the objects. Check out the link in the small note below the code snippet. – Bergi Aug 21 '14 at 10:49
  • Sorry, I don't see how that explains the behavior. Event if I put `return $.when.apply($, deferreds).then(function() {return 1})` I still get the first object passed to `def.resolve()` when I call `getAllFiles().done(function (files) { })` - I expect `files` to be `1` here, but it's not – Max Koretskyi Aug 21 '14 at 11:24
  • _but an array-like object of argument arrays that contain the objects_ - it doesn't seem to be so - `arguments[1][0]` is undefined. – Max Koretskyi Aug 21 '14 at 11:26
  • Uh, are you using one of the very very old jQuery version where `then` was non-standard and you have to use `pipe` instead? – Bergi Aug 21 '14 at 11:34
  • Ah, sorry, it seems that I am. I'm new to the project and for some reason expected jQuery to be the newest one. I'll use `pipe` then. Thanks again! Best! – Max Koretskyi Aug 21 '14 at 11:43