Search code examples
javascriptjqueryajaxjquery-deferreddeferred

Ajax parallel post: Define timeout for each request and get only suceeded


My question is there is a way to define timeout for each parallel ajax post when we used jquery deffered interface. E.g.

parallelPost: function(toUrl1, toUrl2, theData1, theData2, contentType, dataType, successHandler, errorHandelr, completeHandler) {

$.when($.ajax(this.createAjaxCall(toUrl1, theData1, true, headers, 'POST', contentType, dataType,1000)),
               $.ajax(this.createAjaxCall(toUrl2, theData2, true, headers, 'POST', contentType, dataType,2000))).done(function(res1, res2) {
                successHandler(res1, res2);
            }, errorHandelr, completeHandler);
        },
        createAjaxCall: function(toUrl, theData, isAsync, headers, verb, contentType, dataType, timeout, successHandler, errorHandelr, completeHandler) {
            return {
                url: toUrl,
                cache: false,
                type: verb,
                data: theData,
                dataType: dataType,
                timeout: timeout || 0,
                async: isAsync,
                headers: headers,
                contentType: contentType ? contentType : 'application/x-www-form-urlencoded',
                success: successHandler,
                error: errorHandelr,
                complete: completeHandler
            };
        }

The timeouts for each parallel posts were defined 1000 and 2000. My goal to get those responses that were succeeded in defined timeouts. Thus, when the first request was time-outed and second was not, then return only second response.

Via jquery deffered interface if at least one is time-outed fail callback is called.

Is there is a way to define such behavior or may be another interface that provide solution to issue


Solution

  • Here's how I would do this ...

    First, for the general principle, read this answer.

    Now, implement reflect() in the guise of a chainable .jqXhrReflect() method, which returns:

    • on success: jQuery.ajax success args bundled into a single object,
    • on error: a promise resolved with jQuery.ajax error args bundled into a single object.
    (function($) {
        if(!$.$P) {
            $.$P = function() {
                return (this instanceof $.$P) ? this : (new $.$P());
            };
        }
        if(!$.$P.prototype.jqXhrReflect) {
            $.$P.prototype.jqXhrReflect = function() {
                /* A promise method that "reflects" a jqXHR response.
                 * Delivers, on the success path, an object that bundles :
                 * - jqXHR success arguments (data, textStatus, xhr) or 
                 * - jqXHR error arguments (xhr, textStatus, errorThrown).
                 */
                return this.then(
                    function(data, textStatus, xhr) { return { 'data':data, 'textStatus':textStatus, 'xhr':xhr }; },
                    function(xhr, textStatus, errorThrown) { return $.when({ 'xhr':xhr, 'textStatus':textStatus, 'errorThrown':errorThrown }); }
                );
            };
        }
    })(jQuery);
    

    Note: Custom jQuery promise methods are not intuitive

    Then change parallelPost() as follows :

    • to accept ready-formed ajax options,
    • not to accept successHandler, errorHandelr, completeHandler args,
    • to filter the ajax responses in order to separate out results and errors.
    parallelPost: function(ajaxOptions1, ajaxOptions2) {
        return $.when(
            this.ajaxCall(ajaxOptions1),
            this.ajaxCall(ajaxOptions2)
        ).then(function() {
            var args = Array.prototype.slice.call(arguments);
            // here, apply various filters
            return {
                all: args,
                results: args.filter(function(obj) {
                    return obj.data !== undefined;
                }),
                allErrors: args.filter(function(obj) {
                    return obj.errorThrown !== undefined;
                }),
                timeouts: args.filter(function(obj) {
                    return obj.errorThrown && obj.textStatus === 'timeout';
                }),
                otherErrors: args.filter(function(obj) {
                    return obj.errorThrown && obj.textStatus !== 'timeout';
                })
            };
        });
    },
    

    Then change .createAjaxCall() to actually perform the ajax call and transmogrify the response using the .jqXhrReflect() method defined above :

    ajaxCall: function(ajaxOptions) {
        var ajaxDefaults = {
            cache: false,
            type: 'POST',
            dataType: 'JSON', // or whatever
            async: false,
            contentType: 'application/x-www-form-urlencoded'
        };
        return $.ajax($.extend(ajaxDefaults, ajaxOptions)) // $.extend does the necessary magic of merging ajaxDefaults and ajaxOptions.
            .promise($.$P()) // make the .jqXhrReflect() method available.
            .jqXhrReflect(); // call the .jqXhrReflect() method.
    }
    

    Now you can call,

    myObj.parallelPost(
        { url: 'path/to/resource1', timeout: 1000 },
        { url: 'path/to/resource2', timeout: 2000 }
    ).then(function(outcomes) {
        // this success callback is guaranteed to fire and will make the following available :
        // array outcomes.all
        // array outcomes.results
        // array outcomes.allErrors
        // array outcomes.timeouts
        // array outcomes.otherErrors
    });