最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Continue of failure of jQuery Deferred chain - Stack Overflow

programmeradmin2浏览0评论

I'm doing a series of sequential AJAX calls in jQuery, using the usual method of chaining with Deferred. The first call returns a list of values and the subsequent calls are made with those returned list entries. After the first call that returns the list, the subsequent calls may be done in any order, but they must be done one at a time. So this is what I use:

$.when(callWebService()).then(
function (data) {
    var looper = $.Deferred().resolve(),
        myList = JSON.parse(data);
    for (var i in myList) {
        (function (i) {
            looper = looper.then(function () { // Success
                return callWebService();
            }, 
            function (jqXHR, textStatus, errorThrown) { // Failure
                if (checkIfContinuable(errorThrown) == true)
                    continueChain();
                else
                    failWithTerribleError();
            });
        })(i);
    }
});

It turns out that the subsequent calls may fail at times, but I still want to do the remainder of the calls. In my listing, that's what this little bit of inventive pseudo code is meant to do:

if (checkIfContinuable(errorThrown) == true)
    continueChain();
else
    failWithTerribleError();

How on earth do I implement continueChain though? It appears as though a failure on any deferred will cause the rest of the chain to also fail. Instead, I'd like to log the error and continue with the rest of the list.

I'm doing a series of sequential AJAX calls in jQuery, using the usual method of chaining with Deferred. The first call returns a list of values and the subsequent calls are made with those returned list entries. After the first call that returns the list, the subsequent calls may be done in any order, but they must be done one at a time. So this is what I use:

$.when(callWebService()).then(
function (data) {
    var looper = $.Deferred().resolve(),
        myList = JSON.parse(data);
    for (var i in myList) {
        (function (i) {
            looper = looper.then(function () { // Success
                return callWebService();
            }, 
            function (jqXHR, textStatus, errorThrown) { // Failure
                if (checkIfContinuable(errorThrown) == true)
                    continueChain();
                else
                    failWithTerribleError();
            });
        })(i);
    }
});

It turns out that the subsequent calls may fail at times, but I still want to do the remainder of the calls. In my listing, that's what this little bit of inventive pseudo code is meant to do:

if (checkIfContinuable(errorThrown) == true)
    continueChain();
else
    failWithTerribleError();

How on earth do I implement continueChain though? It appears as though a failure on any deferred will cause the rest of the chain to also fail. Instead, I'd like to log the error and continue with the rest of the list.

Share Improve this question asked Oct 20, 2015 at 21:08 Cobus KrugerCobus Kruger 8,6255 gold badges64 silver badges108 bronze badges 4
  • What does continueChain and failWithTerribleError do exactly? – Joseph Commented Oct 20, 2015 at 21:12
  • See stackoverflow./questions/27171161/… , stackoverflow./questions/28131082/… – guest271314 Commented Oct 20, 2015 at 21:19
  • @CobusKruger Try using .always() – guest271314 Commented Oct 20, 2015 at 21:30
  • @JosephtheDreamer They're placeholders. When implemented failWithTerribleError will do whatever needs to be done if the error is not continuable, whether that involves logging the error, showing an alert or whatever. continueChain is the one I'm asking about - I'd like the next web service call to be done as if there never was a failure. – Cobus Kruger Commented Oct 20, 2015 at 21:45
Add a ment  | 

2 Answers 2

Reset to default 5

With Promises/A+ this is as easy as

promise.then(…, function(err) {
    if (checkIfContinuable(err))
        return valueToConinueWith;
    else
        throw new TerribleError(err);
})

Unfortunately, jQuery is still not Promises/A+ pliant, and forwards the old value (result or error) - unless you return a jQuery Deferred from the callback. This works just the same way as rejecting from the success handler:

jDeferred.then(…, function(err) {
    if (checkIfContinuable(err))
        return $.Deferred().resolve(valueToConinueWith);
    else
        return $.Deferred().reject(new TerribleError(err));
})

Recovering from an error in a jQuery promise chain is more verbose than with Promises/A+ implementations, which naturally catch errors in a .catch's or a .then's error handler. You have to throw/rethrow in order to propagate the error state.

jQuery works the other way round. A .then's error handler (.catch doesn't exist) will naturally propagate the error state. To emulate "catch", you have to return a resolved promise, and the chain will progress down its success path.

Starting with an array of items on which you want to base a series of async calls, it's convenient to use Array.prototype.reduce().

function getWebServiceResults() {
    return callWebService().then(function(data) {
        var myList;

        // This is genuine Javascript try/catch, in case JSON.parse() throws.
        try {
            myList = JSON.parse(data);
        }
        catch (error) {
            return $.Deferred().reject(error).promise();//must return a promise because that's what the caller expects, whatever happens.
        }

        //Now use `myList.reduce()` to build a promise chain from the array `myList` and the items it contains.
        var promise = myList.reduce(function(promise, item) {
            return promise.then(function(arr) {
                return callWebService(item).then(function(result) {
                    arr.push(result);
                    return arr;
                }, function(jqXHR, textStatus, errorThrown) {
                    if(checkIfContinuable(errorThrown)) {
                        return $.when(arr); // return a resolved jQuery promise to put promise chain back on the success path.
                    } else {
                        return new Error(textStatus);//Although the error state will be naturally propagated, it's generally better to pass on a single js Error object rather than the three-part jqXHR, textStatus, errorThrown set.
                    }
                });
            });
        }, $.when([])) // starter promise for the reduction, resolved with an empty array

        // At this point, `promise` is a promise of an array of results.

        return promise.then(null, failWithTerribleError);
    });
}

Notes:

  • An overall function wrapper is assumed, function getWebServiceResults() {...}.
  • callWebService() is assumed to accept item - ie the contents of each element of myList.
  • To do its job, checkIfContinuable() must accept at least one argument. It is assumed to accept errorThrown but might equally accept jqXHR or textStatus.
发布评论

评论列表(0)

  1. 暂无评论