2

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.

Isaac
  • 15,783
  • 9
  • 53
  • 76
  • Is `vcfPath` the unique "path" ? – guest271314 Oct 22 '14 at 23:36
  • 1
    Well You'll get detailed information in the `statusTxt` and `errText`, if there was a problem client side or with the request, but if there was a server error, then obviously that information cannot be easily obtained through those parameters. – Wold Oct 22 '14 at 23:37
  • @guest271314 yes, it is (edited to `path`). – Isaac Oct 22 '14 at 23:38
  • @Isaac Could attach `path` to `jqxhr` object at `beforeSend` , then parse `jqxhr` at `.fail()` for specific path ; do stuff for specific path. – guest271314 Oct 22 '14 at 23:40

2 Answers2

3

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/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Hmm, this doesn't seem to be working for me: doing so doesn't add a `myPath` attribute to the jqXHR object I get in when's fail callback. – Isaac Oct 22 '14 at 23:27
  • @Isaac - jQuery is a bit confusing how it passes arguments to `$.when()` (it works differently if there is one promise vs. more than one promise). Anyway, here's a working example of how you could do what I was suggesting. – jfriend00 Oct 22 '14 at 23:41
  • This does not work, however, with `.fail()` (or if it does, I do not see how), which is the case I need the extra information for. – Isaac Oct 22 '14 at 23:51
  • @Isaac - you just have to get the arguments correct for the fail handler and it does work. See the revised code where I've shown you how the arguments work for the fail handler and I've even shown the failed ajax call in the working demo. – jfriend00 Oct 23 '14 at 00:04
  • Ah, fascinating; this works with `$.ajax(…)` but not with `$.get(…)`! Interesting/bizarre; any idea why? – Isaac Oct 23 '14 at 00:21
  • @Isaac - I don't know why, but `$.ajax()` is the superset so you can always use it instead. – jfriend00 Oct 23 '14 at 00:23
  • Could you note this somehow in your answer, and I'll accept it! Thanks! – Isaac Oct 23 '14 at 00:25
  • @Isaac - It seems to work with `$.get()` here: http://jsfiddle.net/jfriend00/m7jcb4wq/ – jfriend00 Oct 23 '14 at 00:27
  • It does indeed…I'm not sure why it wasn't for me. Thanks! – Isaac Oct 23 '14 at 16:32
  • Ah, it doesn't work if you use a `then` after the `get`. – Isaac Oct 23 '14 at 16:39
  • @isaac - that's because the .then() makes a new promise that won't have the custom data on it. – jfriend00 Oct 23 '14 at 19:21
  • If I assign `myPath` a value on that new Promise, that does not carry through to when I need it on `where`'s `fail`. My question includes a `then`, so I don't think this answer as it stands now would help someone who has this same problem. I've "solved" it for now by not using "then" and doing what "then" did in `where`'s `then`… but this seems non-ideal. – Isaac Oct 23 '14 at 20:59
  • @Isaac - you seem to be looking for a magic bullet solution here. As I think you mentioned in your original answer, you can always just store data in some sort of closure variable that is independent of the promise objects. If you want to be independent of those, that is the way to go. You seemed to not want to go that way so I was trying to help you find an alternative - but the alternative has some limitations. – jfriend00 Oct 23 '14 at 21:05
  • @Isaac - I added one more implementation to the end of my answer. – jfriend00 Oct 23 '14 at 21:47
0

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/

guest271314
  • 1
  • 15
  • 104
  • 177