Provide a default 'fail' method for a jQue

2020-07-11 07:21发布

问题:

I am writing a Javascript API client using jQuery. My top level request method looks like this:

function request(method, uri, params, proxies) {
  var deferred = $.Deferred();
  $.ajax({
    data: method == 'GET' ? params : JSON.stringify(params),
    contentType: 'application/json',
    dataType: 'json',
    url: api.root + uri,
    type: method,
    xhrFields: {
      withCredentials: true
    }
  }).done(function(body) {
    deferred.resolveWith(this, [body.data]);
  }).fail(function(xhr) {
    deferred.rejectWith(this, [xhr]);
  });

  return deferred.promise();
},

How can I have a default fail handler for my returned deferred? That is, if the deferred has no other handlers attached to it's fail condition, call a default handler.

I want to do this to have global exception handling in my application, except for the parts that have a specific handling (and will define their own fail handler on the deferred).

回答1:

So, the cleanest way to use jQuery ajax in an API as of 2016 is to return a promise. But, you cannot determine whether a caller has attached an error handler or not to the promise.

So, what I'd suggest is that you just add a new argument to your function that tells the function to NOT apply the default error handling because the caller will take care of the error handling. And, I'd suggest you avoid the promise anti-pattern by just using the existing promise $.ajax() already returns rather than creating your own deferred:

function request(method, uri, params, proxies, skipDefaultErrorHandling){
    // default error handling will be used if nothing is passed
    // for skipDefaultErrorHandling
    var p = $.ajax({
        data: method=='GET'?params:JSON.stringify(params),
        contentType: 'application/json',
        dataType: 'json',
        url:  api.root + uri,
        type: method,
        xhrFields: {
            withCredentials: true
        }
    });
    if (!skipDefaultErrorHandling) {
       // apply default error handling
       p = p.then(null, function(jqXHR, textStatus, errorThrown) {
           // put here whatever you want the default error handling to be
           // then return the rejection with the various error parameters available
           return $.Deferred().reject([jqXHR, textStatus, errorThrown]);
       });
    }

    return p;
};

Then, the caller just decides whether to apply their own error handling or not:

request(...).then(function(data) {
    // success code here
});

Or, you can go with a non-promise failHandler callback that you pass in and your default error handling looks to see if that failHandler was passed in or not. This is hybrid of promises and callbacks and is not something I would normally choose to architect, but since your question asks for something that promises do not support, this is one of achieving that:

function request(method, uri, params, proxies, failHandler){
    // default error handling will be used if nothing is passed
    // for skipDefaultErrorHandling
    var p = $.ajax({
        data: method=='GET'?params:JSON.stringify(params),
        contentType: 'application/json',
        dataType: 'json',
        url:  api.root + uri,
        type: method,
        xhrFields: {
            withCredentials: true
        }
    });
    // apply default error handling
    p = p.then(null, function(jqXHR, textStatus, errorThrown) {
       if (failHandler) {
           // call passed in error handling
           failHandler.apply(this, arguments);
       } else {
           // do your default error handling here
       }
       // then keep the promise rejected so the caller doesn't think it
       // succeeded when it actually failed
       return $.Deferred().reject([jqXHR, textStatus, errorThrown]);
    });

    return p;
};