4

I want to create a function that performs async operation and returns a promise. The action is:

  • downloading external content via AJAX
  • if the content can't be downloaded (e.g. invalid URL) -> reject
  • if the content was downloaded successfully, but I can't parse it -> reject
  • otherwise (content was downloaded and parsed successfully) -> resolve

The code I've got now is pretty sttraightforward:

function fetch(hash){
  return $.ajax({
    method: "GET",
    url: baseURL + "/gists/" + hash
  }).then(function(response) {
    return JSON.parse(response.files['schema.json'].content);
  });
}

it's just a jQuery AJAX call that fetches data from github gist. Currently:

  • when the AJAX can't be resolved -> promise gets rejected - OK
  • when the AJAX was resolved and content was parsed correctly -> promise gets resolved - OK
  • however, if the AJAX was resolved, but the content was invalid, the JSON.parse line throws an error and the promise doesn't get rejected and it should - FAIL

The question is - what is the right design to do that? I could wrap entire thing with a Deferred and handle it manually, but I think there might be an easier solution. Should I do a try-catch? Or something else?

2
  • This issue is caused because jQuery 1.x or 2.x promises are not compliant with the promises spec and are not throw-safe. With a real promise that follows the spec, a throw inside a .then() handler will automatically reject the promise. You can either cast the jQuery promise to a real promise and get the desired behavior or you will have to catch the exception yourself and return a rejected promise. Commented Jul 31, 2016 at 16:18
  • FYI, I would suggest not using fetch() for your function name because newer browsers already have an interface named fetch() described here. Commented Jul 31, 2016 at 17:52

3 Answers 3

2

I could wrap entire thing with a Deferred and handle it manually

That sounds like the deferred antipattern. Don't wrap anything that already uses promises.

I think there might be an easier solution. Should I do a try-catch?

Yes:

.then(function(response) {
  try {
    return JSON.parse(response.files['schema.json'].content);
  } catch (e) {
    return $.Deferred().reject(e);
  }
})

See also Throwing an Error in jQuery's Deferred object.

Or something else?

Actually your original code does work in any reasonable promise implementation, where exceptions become rejections implicitly. It's just jQuery's fail. You might simply want to dodge jQuery's then implementation.

Sign up to request clarification or add additional context in comments.

2 Comments

Perfect answer, thx so much! Just one question about the last paragraph - does that mean that with a different promise implementation, a promise would get rejected automatically if an exception was thrown - and only jQuery doesn't support that? I thought that promises should automatically do that.
@ducin: Yes, exactly that.
1

As has already been described, jQuery 1.x and 2.x promises are not compliant with the promise spec and .then() handlers in those version are not "throw-safe". With a proper promise, the exception will be automatically turned into a rejection.

Bergi has already shown how to manually catch the exception and manually turn it into a rejection in jQuery. But, you could also cast the jQuery promise into a real promise and then get that throw-safe behavior for free. Here's one way to do that:

function fetch(hash){
  return Promise.resolve($.ajax({
    method: "GET",
    url: baseURL + "/gists/" + hash
  })).then(function(response) {
    return JSON.parse(response.files['schema.json'].content);
  });
}

Or, perhaps even better, make a new version of $.ajax() that returns a standard promise and then use that:

$.ajaxStd = function() {
    return Promise.resolve($.ajax.apply($, arguments));
}

function fetch(hash){
  return $.ajaxStd({
    method: "GET",
    url: baseURL + "/gists/" + hash
  }).then(function(response) {
    return JSON.parse(response.files['schema.json'].content);
  });
}

Both of these options mean that your fetch() function now returns a standard promise, not a jQuery promise so it won't have some of the jQuery specific things on it like .abort(), but it will have standards-compliant promise behavior.

2 Comments

in both cases you are wrapping the jQuery promise with native ES6 Promise object - or is it just any promise implementation, such as 3rd party Q or buebird? ES6 is stil not the de facto standard.
@ducin - Any promise library that follows the proper specification will do including third party implementations like Bluebird and Q. ES6 promises are the standard. But, yes they aren't in all browsers in use yet (though they are in all current versions of browsers) so you can use a polyfill.
0

As you clearly mentioned, $.ajax fails only for one of your other cases, hence you can easily handle that case alone using some custom validation. You could possibly include the parse in a try catch block

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.