Problem
Want to brush up my rusty web dev skills. I want to call the twitch API for several channels and eventually call a callback function when all results are in without using a sync call.
I didn’t work with JavaScript for a long time, so I’m not sure if my solution is a good way, especially how I figure out if the callback function should be called.
var Twitch = function() {
var baseUrl = 'https://api.twitch.tv/kraken/streams/';
var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff"];
this.update = function(callback) {
var result = {};
for (var i = 0; i < channels.length; i++) {
var qUrl = baseUrl + channels[i];
var name = channels[i];
$.getJSON(qUrl, success(name, result, callback));
}
}
function success(name, result, callback) {
return function(data) {
result[name] = data;
if (results.length == channels.length) {
callback(result);
}
}
}
};
Solution
I suppose this code is used like:
var twitch = new Twitch();
twitch.update(function(){
// called each time a site responds
});
A few things:
First, I see little benefit of this functionality being written using a constructor. It appears to be a singleton by functionality as it makes no sense spawning multiple instances. Consider a singleton instead:
var Twitch = (function(){
// stuff
return {
update: function(){...}
};
}());
Next is your AJAX. As pointed in the comments, use $.when
to listen to multiple jqXHR objects and call the callback once everyone is resolved. The native counterpart to $.when
is Promise.all
.
Next is to make update
return a promise instead of handing it a callback. This way, your caller is allowed to listen not only for the success but also the failures.
Next is to take advantage of array methods like map
instead of loops.
Your code could be simplified into:
var Twitch = (function(){
var baseUrl = 'https://api.twitch.tv/kraken/streams/';
var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff"];
return {
update: function(){
var promises = channels.map(function(channel){
return $.getJSON(baseUrl + channel).then(function(data){
var result = {};
result[channel] = data;
return result;
});
});
return $.when.apply(null, promises).then(function(){
var args = Array.prototype.slice.call(arguments);
return args.reduce(function(results, result){
return $.extend(results, result);
}, {});
});
}
};
}());
// Usage
Twitch.update().then(function(results){
// Succeeded
}, function(){
// Failed
});
Now if you have ES6 available (via transpiler, or a modern browser), this can be simplified further by using the new syntax and making it more compact.
var Twitch = (function(){
var baseUrl = 'https://api.twitch.tv/kraken/streams/';
var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff"];
return {
update(){
var promises = channels.map(channel => {
return $.getJSON(`${baseUrl}${channel}`)
.then(data => ({[channel]: data}));
});
return Promise.all(promises)
.then((...args) => args.reduce((results, result) => Object.assign(results, result), {}));
}
};
}());
// Usage
Twitch.update().then(function(results){
// Succeeded
}, function(){
// Failed
});