Question

I have searched many very similar questions. I found one that had almost exactly what I was looking for except it did not have any error handling, and upon trying to implement error handling I am running into some issues.

My goal is to create a function that receives an array of names, creates $.ajax requests in a loop pushing the returns promise objects into an array which I then provide to $.when.apply($,arrayOfPromises).then(// etc to resolve a master deferred where all of the promises in arrayOfPromises have been resolved. The end result is to create an array of the data returned from the ajax calls.

I have this so far: http://jsfiddle.net/BGx2h/1/

It's almost exactly what I need. If the array contains pointers to all valid resources, everything seems to work as intended. However, if there is a call to a resource the doesn't exist (such as /test/doesnt.exist.txt in my fiddle), the ajax call for "doesnt.exist" finishes before any of the other calls, the master deferred (connectiondfd) gets resolved before the others calls have a chance to finish (you may have to run the fiddle several times to get this result). Here is what my function looks like currently:

function multiAsync() {
    var i,data=[],connections=[],
        targets=['John.Smith','Jane.Doe','Bob.Someone','doesnt.exist'];

    var connectiondfd = $.Deferred();

    for(i=0;i<targets.length;i++) {
        connections.push($.ajax({
            url:'http://porticium.ca/test/'+targets[i]+'.txt',
            type:'GET',
            async:true,
            timeout:5000
        }).then(function(newData) {
            data.push(newData);
        },function() {
            data.push("NO DATA");
        }));
}

    $.when.apply($,connections)
        .then(
            function() { connectiondfd.resolve(); },
            function() { connectiondfd.resolve(); }
        );


    $.when(connectiondfd).done(function() {
        alert("FINAL: " + data);
    });
}

Would definitely appreciate some help on this one, it has been driving me insane!

Thanks, Rob

Was it helpful?

Solution 2

While the $.Deferred.when function will fire immediately if one of your promisses fails, you can create a 'wrapper' Deferred to handle an array of promisses and dispatch them as they come in then fire the master's own handlers once all are complete, even if some fail and some succeed. This particular function is just copied from a all-purpose tools file I have and uses Underscore for brevity, but the essential pattern is what you need:

    function completed( firstParam ) {

        var args = _.toArray( arguments ),
            i = 0,
            length = args.length,
            pValues = new Array( length ),
            count = length,
            deferred = length <= 1 && firstParam && $.isFunction( firstParam.promise ) ? firstParam : $.Deferred(),
            promise = deferred.promise(),
            state = 'resolved';

        function alwaysFunc( i ) {

            return function ( value ) {

                args[ i ] = arguments.length > 1 ? _.toArray( arguments ) : value;
                state = ( this.state && this.state() === 'rejected' ) ? 'rejected' : state;
                if ( !( --count ) ) deferred[ ( state === 'rejected' ? 'reject' : 'resolve' ) + 'With' ]( deferred, args );

            };

        }

        function progressFunc( i ) {

            return function ( value ) {

                pValues[ i ] = arguments.length > 1 ? _.toArray( arguments ) : value;
                deferred.notifyWith( promise, pValues );

            };

        }

        if ( length > 1 ) {

            for ( ; i < length; i++ ) {

                if ( args[ i ] && args[ i ].promise && $.isFunction( args[ i ].promise ) )
                    args[ i ].promise().always( alwaysFunc( i ) ).progress( progressFunc( i ) );

                else --count;

            }

            if ( !count ) deferred.resolveWith( deferred, args );

        } else if ( deferred !== firstParam ) deferred.resolveWith( deferred, length ? [ firstParam ] : [] );

        return promise;

    }

Anyway, you create and attach handlers to individual requests/promisses as usual, then pass them all through this function via apply. Each request's state is handled separately and the function keeps track of how many are still unresolved. Once all are resolved, it fires it's own resolution based on the collection. Even if one or all of the component promisses fail, all are still executed and the master Deferred waits till they're all resolved. It doesn't handle adding further promisses/deferreds after the initial call - create all the one's you need, then pass them to this function.

I can't take credit for the script: it was 'passed down' to me via a coworker - whom I assume got it from some place else but was the worst person in the world for keeping comments/attributions in code. If anyone recognizes the code and can point me in the direction of the author...

OTHER TIPS

So I was playing with this more and studying the function that was provided in the answer I accepted (thanks again!) and I managed to find out another solution using a deferred that suits my purposes.

I've created a function called whenAll that accepts an array of promises, creates a master deferred, and adds done and fail callbacks to each promise that push the returned data, or an error message into an array and then checks the length of the data array vs the length of the provided promises array, resolving when they are the same.

Here it is:

function whenAll(promises) {
    var i,data=[],dfd=$.Deferred();
    for(i=0;i<promises.length;i++) {
        promises[i].done(function(newData) {
            data.push(newData);
            if(data.length==promises.length) {
                dfd.resolve(data);
            }
        }).fail(function() {
            data.push("NO DATA");
            if(data.length==promises.length) {
                dfd.resolve(data);
            }            
        });
    }
    return dfd.promise();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top