0

Let me just start this by saying I've done a series of searches online, but can't seem to piece it together.

Requirements: Use jQuery :(

On click, I am using a .getJSON call to get an object with several layers.

Here's an example of the data:

 myObj = {
title: 'some name',
items: [
    {
        name: 'item-1',
        url: '/item-1'
    },
    {
        name: 'item-2',
        url: '/item-4'
    },
    {
        name: 'item-3',
        url: '/item-4'
    },
    {
        name: 'item-4',
        url: '/item-4'
    },
]

}

I want to loop through all of the urls, and call an .ajax operation on them, and then store the new data I get back in their respective objects.

It would look like this:

myObj = {
title: 'some name',
items: [
    {
        name: 'item-1',
        url: '/item-1',
        properties: {//whole new set of data from url}
    },
    {
        name: 'item-2',
        url: '/item-4',
        properties: {//whole new set of data from url}
    },
    {
        name: 'item-3',
        url: '/item-4',
        properties: {//whole new set of data from url}
    },
    {
        name: 'item-4',
        url: '/item-4',
        properties: {//whole new set of data from url}
    },
]

}

Once all of that is complete and each object has this new bit of data, I then want to do something with the myObj, like render it to a jquery template (ugh), but the new data MUST be inside of each item.

Here's what I have so far:

var myItems = myObj.items;
$(myItems).each(function(index, item) {    
      var itemUrl = '/somestuff/' + item.url + '.js';
      $.getJSON(itemUrl).done(function(itemData) {
        item.data = itemData;                    
      });
    }).promise().done(function() {//render data to template})

The only problem I'm having is that sometimes the data doesn't exist yet (item.properties) when the template renders, and therefore cannot render undefined.

I've tried unsuccessfully chaining .done(), and have now come across using .when(), but don't know how to write the line of code to make .when() work properly.

Any help is appreciated, and I'd be happy to clarify details.

ADyson
  • 57,178
  • 14
  • 51
  • 63

1 Answers1

0

If you capture the Promise (technically a jQuery Deferred object, actually) generated by each AJAX request, and add them to an array, then you can call .when() to execute some code once all of the Promises are resolved. Something like this (untested):

var myItems = myObj.items;
var promises = [];
$(myItems).each(function(index, item) {    
  var itemUrl = '/somestuff/' + item.url + '.js';
  var p = $.getJSON(itemUrl);
  p.then(function(itemData) {
    item.data = itemData;
    return itemData;
  });
  promises.push(p);
});

$.when.apply($, promises).then(function() { //render data to template...

This is probably preferable to chaining the done() callbacks, because it still allows the requests to execute in parallel, which is likely to be faster (although this is somewhat dependent on the server, but that's a separate issue).

ADyson
  • 57,178
  • 14
  • 51
  • 63
  • Please don't use `.done()` in modern code. Use `.then()` and the promise you push into the array should be the one returned from `.then()`, not `p`. – jfriend00 Sep 19 '19 at 00:42
  • @jfriend00 why, out of interest? I can't see what difference it would make in this instance – ADyson Sep 19 '19 at 08:11
  • Propagating the older non standard jquery style does nobody any good when the standard way exists. The best answers teach the best way to solve a problem. – jfriend00 Sep 19 '19 at 08:17
  • @jfriend00 I understand it's not a standard in the Promise spec (despite all the major browsers supporting it anyway in native Promise objects) but jQuery hasn't deprecated or removed it. Since OP is using jQuery, then adhering to the native Promise spec is somewhat irrelevant. The code I've written is supported and correct. Do you have a reason why .then() would actually be advantageous in this scenario? "It's a bit newer" isn't, by itself, really a reason IMHO. If you have a technical explanation rather than an assertion or opinion that would be really helpful - I'm always keen to learn. – ADyson Sep 19 '19 at 08:39
  • Not irrelevant at all. Lots of other client side code that uses standard promises that `.done()` is not interoperable with. Better to use one system only since jQuery supports the standard way. Teaching the interoperable, standard way is better than teaching the non-interoperable proprietary way. If you want to dig in and ignore the suggestion and refuse to make your answer better, that is your right. I disagree and thought the OP should know a better way. – jfriend00 Sep 19 '19 at 08:45
  • @jfriend00 I'm not digging in, I just wanted a better reason than "new is better than old". Given that all the browsers appear to implement .done() in vanilla Promises, even though it's not officially a standard, can you point to a practical example where code wouldn't "support" done and what would that mean in reality? I'm happy to change the answer, just be useful to know precisely what situation you're thinking of – ADyson Sep 19 '19 at 11:42
  • I don't see any native promise support for `.done()` in Chrome v76. Didn't work for me here: https://jsfiddle.net/jfriend00/zb4p5kfe/. This isn't about new or old, it's about recommending the documented and widely supported way to code with promises. I have made my point and will move on now. – jfriend00 Sep 19 '19 at 15:41
  • @jfriend00 and I've taken your point and changed the answer. But if your commentary wasn't about new for old, then I'd like to just point out that commenting along the lines of "modern" code and "older" jquery version certainly gave that impression. It took me to interrogate you before you gave a clearer reasoning. – ADyson Sep 19 '19 at 15:54
  • Well "modern" jQuery (v3.0+) supports `.then()`, older jQuery did not so you could not use `.then()` with older jQuery code. That's why I recommended it in modern jQuery code, not in "old" jQuery code. – jfriend00 Sep 19 '19 at 16:00