Ignoring AJAX errors with promises array and $.when
Asked Answered
M

2

8

I have the following code that gets the JSON from an array of YouTube video ids. It works great when all the videos exists and the query is successful. It sends several getJSON request, and when all of them are done... the $.when.done() fires and I can process the resulting data.

var
  results  = {},
  promises = [];

$(document).ready(function() {

  var
    vids = [
    'ozj2-bnTL3s',
    'EAZ4Tlt8MQ4',
    'Xn9o7cxqVoA'
    // ,'this-videoid-doesnot-exists'
  ],
  url = 'http://gdata.youtube.com/feeds/api/videos/{{vid}}?v=2&alt=json';

  $.each(vids, function(idx, vid){
    var
      u = url.replace('{{vid}}', vids[idx]),
      p = null;

    p = $.getJSON( u ).done(function(data) {
      results[vid] = data.entry;
    });

    promises.push(p);
  });

  $.when.apply($, promises).done(function(){
    console.log(results);
  });

});

But... In the final app I don't control if all the videos still exists in YouTube, I've realized that sometimes one (or several) of the videos in the list may have been deleted... or the id that I get from the DB is incorrect.

Is there any way that I could safely add to the results variable only the videos that where successful whiteout trigger the $.when.fail()? and waiting that all the queries had finished...

I mean, my final goal is to get the data from the videos that exist (those that its data was successfully retrieved), and, somehow to ignore those that don't exist or where unavailable... and I don't figure right now how to do it.

Any idea / approach will be appretiated. TIA!
You can find the code in this JSFiddle

Mcvay answered 12/5, 2014 at 20:6 Comment(1)
See #23318055Sardou
S
8

Unfortunately, jQuery doesn't come with this functionality.

Gladly, you can implement it yourself pretty easily.

var url = 'http://gdata.youtube.com/feeds/api/videos/{{vid}}?v=2&alt=json';
function getVideo(vid){
    var u = url.replace('{{vid}}', vid);
    return $.getJSON( u ).then(function(res){
         return {video:vid,result:res.entry};
    });
}
var promises = ['ozj2-bnTL3s','EAZ4Tlt8MQ4',"doesn'texist"].map(getVideo);

some(promises).then(function(results){
    for(var i = 0; i < results.length; i++) {
        console.log(results[i]); // log
    }
});



// get a hook on when all of the promises resolve, some fulfill
// this is reusable, you can use this whenever you need to hook on some promises
// fulfilling but all resolving.
function some(promises){
    var d = $.Deferred(), results = [];
    var remaining = promises.length;
    for(var i = 0; i < promises.length; i++){
        promises[i].then(function(res){
            results.push(res); // on success, add to results
        }).always(function(res){
            remaining--; // always mark as finished
            if(!remaining) d.resolve(results);
        })
    }
    return d.promise(); // return a promise on the remaining values
}

Here's a working JSFiddle of the result.

Sardou answered 13/5, 2014 at 8:5 Comment(3)
Note I created the aggregate method using a Deferred object, in better libraries that handle errors in a more predictable way, there would have been a way to avoid the explicit deferred here but given the alternatives this is the cleanest I could think of with jQuery promises.Sardou
If you also need to do something with the rejections (for example, detect them gracefully), have a look at the link to the settle implementation in the question comment I left.Sardou
Thx a lot Benjamin, this fits perfectly what I was trying to achieve, I'll take a look to the link too.Mcvay
F
-1

In a purely javascript Promise implementation (or a specialised library like Q or another), you could do this:

function getVideo(id) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', 'url = 'http://gdata.youtube.com/feeds/api/videos/' + id + '?v=2&alt=json');
    request.onload = function() {
      if (request.status == 200) {
        resolve(request.response); //we get the data here. So, resolve the Promise
      } else {
        reject(Error(request.statusText)); //if status is not 200 OK, reject.
      }
    };

    request.onerror = function() {
      reject(Error("Error fetching data.")); //error occurred, reject the Promise
    };

    request.send(); //send the request
  });
}

function fetchVideos() {
  var vids = [
    'ozj2-bnTL3s',
    'EAZ4Tlt8MQ4',
    'Xn9o7cxqVoA'
    // ,'this-videoid-doesnot-exists'
  ]
  var promises = [];
  for (var i in vids) {
    promises.push(getVideo(vids[i]));
  }
  Promise.all(promises).then(function(dataArr) {
    dataArr.forEach(function(data) {
      console.log(data.entry);
    });
  }).catch(function(err){
      console.log(err);
  });
}
Foreworn answered 12/5, 2014 at 21:50 Comment(1)
This would not help OP at all as Promise.all requires all promises to resolve and OP needs some.Sardou

© 2022 - 2024 — McMap. All rights reserved.