Get more information from a failed JQuery jqXHR re

2019-07-17 23:27发布

问题:

I'm using JQuery's $.when and $.get to fetch some data and do something with it. When there is an error fetching it or doing something with it, I want to handle that error, and handle it differently depending on which data I was fetching/at which path I was fetching it.

Unfortunately, the information passed to $.when().fail(...) is scant, and doesn't include that information. How might I bubble up that information from the deferrable in a non-hacky[1] way?

$.when(get(path1), get(path2)
  .done(function(parsed1, parsed2) {
    // do something with this date
  }).fail(function(jqXHR, statusTxt, errTxt) {
    // do something depending on which path failed...
  });

function get(path) {
  var parser = U.parser();
  return $.get('/getter' + path)
    .then(function(data) {
      return parser(data);
    });
}

[1]: Sure, I could close around the path and some global objects I want to handle failure with, and handle it in a .fail() on the Deferred that $.get returns. I could also pass up a Deferred that doesn't failed, but wraps the failure in some way, but that also feel very hacky.

回答1:

Since the jqXHR object is one of the things passed back to $.when() it seems you can put things in there related to your request. For example, if the path is something you want, you could do this:

function get(path) {
  var parser = U.parser();
  var p = $.get('/getter' + vcfPath)
    .then(function(data) {
      return parser(data);
    });
  p.myPath = path;
  return p;
}

Then, in your $.when() handler, you will have the jqXHR object that you can extract parameters from.

Note, $.when() rejects as soon as any single request rejects. If you actually want all requests to continue and you want to know when all have been fullfilled (whether resolved or rejected), you can't use $.when() for that. jQuery doesn't natively offer that function. You can either build it yourself or use an external promise library that offers that (I use Bluebird which has Promise.settle().

Here's a working example:

var data = {
    json: JSON.stringify({
        text: 'some text',
        array: [1, 2, 'three'],
    }),
    delay: 1
}


function runAjax(data, value) {
    var p = $.ajax({
        url:"/echo/json/",
        data:data,
        type:"POST",
    });
    // put something on the jqXHR object for retrieval later
    p.myData = value;
    return p;    
}

function runAjaxFail(data, value) {
    var p = $.ajax({
        url:"/echo/junk/",
        data:data,
        type:"POST",
    });
    // put something on the jqXHR object for retrieval later
    p.myData = value;
    return p;    
}

$.when(runAjax(data, "Hello"), runAjax(data, "GoodBye"), runAjaxFail(data, "My Fail Message")).then(function(r1, r2) {
    // for each arg [0] is data, [1] is status, [2] is jqXHR object
    log(r1[2].myData);   // logs "Hello"
    log(r2[2].myData);   // logs "GoodBye"
}, function(jqXHR, textStatus, errorThrown) {
    // fail handler
    log("fail: " + jqXHR.myData);
});

Working demo: http://jsfiddle.net/jfriend00/g8z353cz/


Here's another approach that is fully supported in the promise world. This takes over the resolution of the ajax promise so that you can put exactly whatever data you want in it. In this particular implementation, I chose to always resolve and just use the data in the resolved value to tell you which ajax calls succeeded or failed, but you could use rejection with a value too if you wanted. That is guaranteed to be propagated all the way back:

function get(path) {
    var parser = U.parser();
    var d = $.Deferred();
    $.get('/getter' + path)
        .then(function(data) {
            d.resolve(parser(data));
        }, function(jqXHR) {
            // error handler
            d.resolve({path: path, jqXHR: jqXHR});
        });
    return d.promise();
}

And, you can see this sort of implementation here: http://jsfiddle.net/jfriend00/c0u40gqh/



回答2:

Try

Note, stops on first error , though can implement workaround for this. Below primarily for possible custom error pattern

var data = {
    "path1" : function() { return {"path1" : [1,2,3]} },
    "path2" : function() { return {"path2" : [7, 8, 9] }},
    "path1Error" : function() {
        console.log("path1 error")
     },
     "path2Error" : function() {
        console.log("path2 error")
     }
};

    $.ajaxSetup({
      beforeSend: function(jqXHR, settings) {
        var path = JSON.parse(
            decodeURIComponent(settings.data.split("=")[1])
        );
          jqXHR.path = (path + "Error") in data 
          ? data[path + "Error"] 
          : path + "Error";
      }
    });

$.when(get("path1"), get("path2"))
  .done(function(parsed1, parsed2) {
      //console.log(parsed1, parsed2)
    // do something with this date
  })
  .fail(function(jqXHR, statusTxt, errTxt) {      
      jqXHR.path()
    // do something depending on which path failed...
  });

function get(path) {
    var parser = function (p) { return data[p]()};
    // `error`
    return $.post("/echo/jsons/", {json:JSON.stringify([path]) })
    .then(function(data) {
      return parser(data[0]);
    });
}

jsfiddle http://jsfiddle.net/guest271314/o3Lk73wg/