3

Re: https://github.com/tildeio/rsvp.js

What's the best way to integrate RSVP.js with jQuery's $.ajax? After doing some reading and research, I see that in Ember there is active development of a wrapper around it... https://github.com/emberjs/ember.js/pull/4148

Anyone with experience with this use-case of promises?

I'm using RSVP.js because I'm handling a dynamic array of promises through the .all() method.

Each of those promises from the array has its own promise that is fulfilled once its done polling for data from a function recursively. I'm having issues considering how I should structure the code.

The use-case if you're interested is that I'm sending an AJAX request to my API for a report pertaining to a specific resource that returns a list of URL endpoints that it should hit for more report data about its child resources. Each of those resources then returns a JSON object with report data for a specific day and another URL with params for the next day (or group of days). This then keeps polling for data with "next" from the same endpoint until there's nothing left.

Thanks in advance to anyone who can help!

Also, if you have any guidance on how to format this code so it's more readable and maintainable, I'd love to hear.

Code:

url = "http://localhost:3000/api/foo_resources/1/reports/bar"

var headers = {
    "Accept": 'application/vnd.xps+json; version=1', // Headers for API access
    "X-User-Email": 'example@company.com',
    "X-User-Token": '1234abcd',
}

$.ajax({
    type: 'GET',
    url: url,
    headers: headers,
    dataType: 'json',
    xhrFields: {
        withCredentials: true
    }
}).then(function(response) {

    // the ajax request would return a long list of bucket like this with endpoints to hit {
    // {
    //   "buckets": [
    //     "http://localhost:3000/api/foos_nested_resources/1/reports/bar"
    //     "http://localhost:3000/api/foos_nested_resources/2/reports/bar"
    //     "http://localhost:3000/api/foos_nested_resources/3/reports/bar"
    //   ]
    // }

    var promises = response.buckets.map(function getReportData(bucket) {

        return new RSVP.Promise(function(resolve, reject) {
            var recursiveRequest = function(bucket) {
                $.ajax({
                    type: 'GET',
                    url: bucket,
                    headers: headers,
                    dataType: 'json',
                    xhrFields: {
                        withCredentials: true
                    }

            // This is the report that comes back, obviously truncated significantly for this example
            // {
            //   reports: [
            //     { id: 1, datapoint_a: 5438, datapoint_b: 1389 },
            //     { id: 2, datapoint_a: 4336, datapoint_b: 2236 }
            //   ],
            //   next: "http://localhost:3003/api/nexted_foo_resources/1/reports/bar?ends=2014-02-06&starts=2014-01-24"
            // }

                }).done(function(response) {
                    $.merge(reports, response.reports); // I could use concat here but I prefer readability

                    if (response.next) {
                        recursiveRequest(response.next);
                    } else {
                        resolve(reports);
                    }
                }).error(function(error) {
                    reject(error);
                });
            };

            recursiveRequest(bucket);
        });
    });

    RSVP.all(promises).then(function(data) {
        console.dir(data);
    }).catch(function(error) {
        console.dir(error);
    })
})
Stan Dyro
  • 33
  • 3
  • jquery has the $.when() which lets you handle dynamic number of promises... have you tried it – Arun P Johny Feb 06 '14 at 00:55
  • Thanks Arun, I'm going to try that out and get back to you. Hopefully that'll make this a little clearer and cleaner. – Stan Dyro Feb 06 '14 at 01:25
  • Ooh, bad suggestion from Arun above, using RSVP is to get away from jquery usage, and is very different from Deferred. in RSVP you use pretty much .then and .catch only. – blamb May 19 '15 at 02:25

2 Answers2

4

With the caveat that I have not tested this code (since I don't have your API handy), I think something like the following would be closer to idiomatic usage of RSVP:

var initialUrl = "http://localhost:3000/api/foo_resources/1/reports/bar";

var headers = {
  "Accept": 'application/vnd.xps+json; version=1', // Headers for API access
  "X-User-Email": 'example@company.com',
  "X-User-Token": '1234abcd',
};

function rsvpAjax(opts){
  return new RSVP.promise(function(resolve, reject){
    var defaultOpts = {
      type: 'GET',
      headers: headers,
      dataType: 'json',
      xhrFields: {
        withCredentials: true
      }
    };
    $.ajax($.extend({}, defaultOpts, opts, {
      success: function(json) {
        resolve(json);
      },
      error: function(jqXhr, textStatus, errorThrown){
        reject({ jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown});
      }
    }));
  });
}

function requestBucket(bucket){
  return rsvpAjax({ url: bucketUrl }).then(bucketResponseProcessor(bucket));
}

function bucketResponseProcessor(bucket){
  return function(response){
    $.merge(bucket.reports, response.reports);
    if (response.next) {
      bucket.url = response.next;
      return requestBucket(bucket);
    } else {
      return bucket.reports;
    }    
  };
}

rsvpAjax({ url: initialUrl }).then(function(response) {
  return RSVP.all(response.buckets.map(function(bucketUrl){
      var bucket = { url: bucketUrl, reports: [] };
      return requestBucket(bucket).then(processBucketResponse);
  }));
}).then(function(reports) {
  console.dir(data);
}).catch(function(error) {
  console.dir(error);
});
Luke Melia
  • 8,389
  • 33
  • 41
  • Thanks Luke! Ended up implementing using $.when for simplicity, but might change once it is migrated to an Ember app. – Stan Dyro Feb 07 '14 at 21:35
0

I think what you are looking for is Ember.RSVP.Promise.cast()

The examples show it with $.getJSON(), but it should work with any jQuery Ajax method, as they all return Deferred objects.