I would like to apply a condition to each deferred request within a $.when() function (before the request is made). However placing an if condition inside $.when returns an error.
What would be the proper way to do what I essentially describe bellow?
$.when(
if(var1) {
$.getJSON(url1, function(data) {...}),
},
if(var2) {
$.getJSON(url2, function(data) {...}),
},
if(varN) {
$.getJSON(urlN, function(data) {...}),
},
).then(function() {
...
});
You can simply construct an array of AJAX promises instead. After that, use $.when.apply($, <yourArray>). To illustrate the solution, here is an example based on the code you have provided:
// Construct array to store requests
var requests = [];
// Conditionally push your deferred objets into the array
if(var1) requests.push($.getJSON(url1, function(data) {...}));
if(var2) requests.push($.getJSON(url2, function(data) {...}));
if(var3) requests.push($.getJSON(url3, function(data) {...}));
// Apply array to $.when()
$.when.apply($, requests).then(function() {
// Things to do when all is done
});
What that in mind, here is a code snippet that shows a proof-of-concept example: I am using dummy JSON returned by JSONPlaceholder:
$(function() {
// Construct array to store requests
var requests = [];
// Conditional vars
var var1 = true,
var2 = false,
var3 = true;
// Conditionally push your deferred objets into the array
if (var1) requests.push($.get('https://jsonplaceholder.typicode.com/posts/1', function(data) { return data;
}));
if (var2) requests.push($.get('https://jsonplaceholder.typicode.com/posts/2', function(data) { return data;
}));
if (var3) requests.push($.get('https://jsonplaceholder.typicode.com/posts/3', function(data) { return data;
}));
// Apply array to $.when()
$.when.apply($, requests).then(function(d) {
// Log returned data
var objects = arguments;
console.log(objects);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You can use ternary operator in $when(). The below code worked for me:
var includeX = true, includeY = false;
var x = $.get('check.html');
var y = $('div').animate({height: '200px'}, 3000).promise(); // you may use an ajax request also here
$.when(includeX ? x : '', includeY ? y: '').done(function(){
console.log(x);
console.log(y);
console.log('done');
})
Related
I'm trying to make my page wait untill all data is loaded and put into the right arrays. I load the data using 3 seperate ajax calls but since the loading sometimes takes a little too long, the page continues while having empty arrays, and I need this data before I can do anything on the page.
I've been looking into the jQuery.when function but I can't seem to get it working.
this is 1 of the ajax calls:
function getWerktijden(){
var AllArrays = [];
var dagen = [];
var start = [];
var end = [];
$.ajax({
type: "GET",
url: '#Url.Action("getWerktijden")',
data: {
id: #Model.ID,
dag: d,
maandNr: m,
jaarNr: y,
getJson: true,
},
success: function (result) {
//console.log(result);
for(var v = 0; v < result.length; v++){
var resultItem = result[v];
var ingangsDatum = resultItem.activatieDatum;
var uitgangsDatum = resultItem.stopDatum;
if(ingangsDatum != ""){
ingangsDatum = new Date(changeDateTimeFormat(ingangsDatum));
ingangsDatum = ingangsDatum.toString('yyyy-MM-dd HH:mm:ss');
};
if(uitgangsDatum != ""){
uitgangsDatum = new Date(changeDateTimeFormat(uitgangsDatum));
uitgangsDatum = uitgangsDatum.toString('yyyy-MM-dd HH:mm:ss');
};
dagen.push({
werktijdenID: resultItem.id,
ingang: ingangsDatum,
uitgang: uitgangsDatum,
maandag: resultItem.maandag,
dinsdag: resultItem.dinsdag,
woensdag: resultItem.woensdag,
donderdag: resultItem.donderdag,
vrijdag: resultItem.vrijdag,
zaterdag: resultItem.zaterdag,
zondag: resultItem.zondag
});
start.push({
werktijdenID: resultItem.id,
ma_van: resultItem.ma_van,
di_van: resultItem.di_van,
wo_van: resultItem.wo_van,
do_van: resultItem.do_van,
vr_van: resultItem.vr_van,
za_van: resultItem.za_van,
zo_van: resultItem.zo_van,
})
end.push({
werktijdenID: resultItem.id,
ma_tot: resultItem.ma_tot,
di_tot: resultItem.di_tot,
wo_tot: resultItem.wo_tot,
do_tot: resultItem.do_tot,
vr_tot: resultItem.vr_tot,
za_tot: resultItem.za_tot,
zo_tot: resultItem.zo_tot,
})
}
}
});
AllArrays.push(dagen, start, end);
return AllArrays;
};
Then I call the function on a variable to return those results into that variable and check wether it's done with the jQuery.when function but the page continues no matter if the function has completed.
var allArrays = getWerktijden();
var event = getMeldingen(); //I put this here to show I have more ajax calls
var ziektedatums = getZiektedatums(); //I put this here to show I have more ajax calls
$.when.apply($, allArrays, event, ziektedatums).then(function(){
dagenPerWeek = allArrays[0];
startPerDag = allArrays[1];
endPerDag = allArrays[2];
}).done(function(){
console.log(dagenPerWeek, startPerDag, endPerDag, event, ziektedatums);
});
Can somebody explain to me what I am doing wrong?
Thanks!
Return the $.ajax promise instead of the array from each function
The array will be immediately returned before the ajax is complete as it is asynchronous. Also an array is not a promise so $.when won't wait for it to be populated
It's also easier to use Promise.all() vs $.when() in modern browsers
So it will look something like:
function getWerktijden() {
var AllArrays = [];
var dagen = [];
var start = [];
var end = [];
// return the promise
return $.ajax({ /* config options*/ })
.then(function(result) {
// do the processing into various arrays
// return to be used in next part of promise chain
return AllArrays;
});
}
Promise.all([getWerktijden(), getMeldingen(), getZiektedatums()])
.then(function(results){
// results will be array of whatever is returned from `then()`
// in each function and is in same order as they are called
var getWerktijden_Arrays = results[0],
dagenPerWeek = getWerktijden_Arrays[0]
console.log(dagemPerWeek);
})
I am trying to apply the modular JS pattern in my code, but am having a hard time implementing promises. I am used to to promises in 1 line using "then", but now I have separate functions and each one is calling the server and returning a value to the other function, I don't know how I can do this. I am confused how I can use done & resolve at the same time.
Here's my code below:
//I want to call a function, makeLinksObject(), which will call the another function that calls the server
var formattedObject = makeLinksObject();
formattedObject.done(function (renderedObject) {
render(renderObject);
})
function makeLinksObject() {
//here I want to call another function that will call the server
var dfd = getLastTimeUpdated();
var linksArray = [];
var linksObject = {};
//get site updated date
dfd.done(function (dateUpdated) {
$.each(links, function (index, value) {
var linkObject = {};
obj.Title = value.Title.toLowerCase();
linksArray.push(obj);
});
linksObject = {
lblcallerId: "some value here"
links: linksArray
}
}); // end done
return dfd.resolve(linksObject);
}
function getLastTimeUpdated() {
var modificationUrl = "serverurl"
dfd = $.ajax({
url: modificationUrl,
method: "GET",
headers: {
"accept": "application/json;odata=verbose"
}
});
dfd.done(function(data){
dfd.resolve(data.d.LastItemModified);
})
return dfd.promise();
}
How do I return the value from server from function 3, to be used in function 2, and the result of function 2, to be used in function 1, then I can draw my html in function 1.
Currently, I am having an error in the second function and it's not recognizing my deferred object.
I thought about writing code that will have nested then, then, but I want to use modular code to make my code organized. Any help would be appreciated.
$.ajax() returns a jQuery promise object, $.Deferred() is not necessary and can be removed; substitute .then() for .done() where you want to return a value other than the original promise value returned from $.ajax(), use return within function calls and .then(). Note, you could also include error handling to the pattern by chaining .fail() to then last .then() in each chain
var formattedObject = makeLinksObject();
formattedObject
.done(function(renderedObject) {
render(renderObject);
})
function makeLinksObject() {
var dfd = getLastTimeUpdated();
var linksArray = [];
var linksObject = {};
return dfd.then(function(dateUpdated) {
$.each(links, function(index, value) {
var linkObject = {};
obj.Title = value.Title.toLowerCase();
linksArray.push(obj);
});
linksObject = {
lblcallerId: "some value here",
links: linksArray
}
})
.then(function() {
return linksObject
});
}
function getLastTimeUpdated() {
var modificationUrl = "serverurl"
return $.ajax({
url: modificationUrl,
method: "GET",
headers: {
"accept": "application/json;odata=verbose"
}
})
.then(function(data) {
return data.d.LastItemModified;
})
}
I have this code in a factory:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var self = this;
var deferred = $q.defer();
$timeout(function () {
$http.get('data/quran.json').success(function (data) {
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
var promise = self.getVerse(value.surah, value.ayah).then(function (res) {
var verse = {
surah: value.surah,
ayah: value.ayah,
text: res
};
response.push(verse);
}, function (err) {
console.log(err);
});
promises.push(promise);
});
});
}, 30);
$q.all(promises).then(function() {
deferred.resolve(response);
});
return deferred.promise;
},
Please note that everything is working fine the verse object is returning properly. However, when I use this in a controller using .then(res). res returns [] instead of the array filled with the verse objects.
Can anyone point out why? Thanks!
The short answer is because your $q.all runs before $timeout & before the $http embedded in $timeout. Let's boil your original code down to its relevant components:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var deferred = $q.defer();
// ...irrelevant stuff that will happen after a $timeout
// this happens IMMEDIATELY (before $timeout):
$q.all(promises).then(function() { // wait for empty promise array
deferred.resolve(response); // resolve with empty response array
}); // side note: this is a broken chain! deferred.promise can't reject
return deferred.promise; // send promise for empty array
}
See the problem? If for some odd reason you need to keep that $timeout, here's the fix with substantial promise refactoring & removing the awful jquery-inspired non-promisy success syntax):
getAyahsByJuz: function (juzIndex) {
var self = this;
// $timeout itself returns a promise which we can post-process using its callback return value
return $timeout(function () {
// returning the $http promise modifies the $timeout promise
return $http.get('data/quran.json').then(function (response) { // you never used this response!
var versePromises = [];
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
// we'll push all versePromises into an array…
var versePromise = self.getVerse(value.surah, value.ayah).then(function (res) {
// the return value of this `then` modifies `versePromise`
return {
surah: value.surah,
ayah: value.ayah,
text: res
};
});
versePromises.push(versePromise);
});
return $q.all(versePromises); // modifies $http promise — this is our ultimate promised value
// if a versePromise fails, $q.all will fail; add a `catch` when using getAyahsByJuz!
});
}, 30);
}
However, there is still a huge issue here… why aren't you using the server response of your $http call anywhere? What is the point of that first call?
Also I find that $timeout to be extremely suspicious. If you need it then it's likely there's something bad going on elsewhere in the code.
I have some code that will dynamically generate an AJAX request based off a scenario that I'm retrieving via an AJAX request to a server.
The idea is that:
A server provides a "Scenario" for me to generate an AJAX Request.
I generate an AJAX Request based off the Scenario.
I then repeat this process, over and over in a Loop.
I'm doing this with promises here: http://jsfiddle.net/3Lddzp9j/11/
However, I'm trying to edit the code above so I can handle an array of scenarios from the initial AJAX request.
IE:
{
"base": {
"frequency": "5000"
},
"endpoints": [
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/alvarengarichard",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
},
{
"method": "GET",
"type": "JSON",
"endPoint": "https://api.github.com/users/dkang",
"queryParams": {
"objectives": "objective1, objective2, objective3"
}
}
]
This seems like it would be straight forward, but the issue seems to be in the "waitForTimeout" function.
I'm unable to figure out how to run multiple promise chains. I have an array of promises in the "deferred" variable, but the chain only continues on the first one--despite being in a for loop.
Could anyone provide insight as to why this is? You can see where this is occuring here: http://jsfiddle.net/3Lddzp9j/10/
The main problems are that :
waitForTimeout isn't passing on all the instructions
even if waitForTimeout was fixed, then callApi isn't written to perform multiple ajax calls.
There's a number of other issues with the code.
you really need some data checking (and associated error handling) to ensure that expected components exist in the data.
mapToInstruction is an unnecessary step - you can map straight from data to ajax options - no need for an intermediate data transform.
waitForTimeout can be greatly simplified to a single promise, resolved by a single timeout.
synchronous functions in a promise chain don't need to return a promise - they can return a result or undefined.
Sticking with jQuery all through, you should end up with something like this :
var App = (function ($) {
// Gets the scenario from the API
// sugar for $.ajax with GET as method - NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario2');
};
var checkData = function (data) {
if(!data.endpoints || !data.endpoints.length) {
return $.Deferred().reject('no endpoints').promise();
}
data.base = data.base || {};
data.base.frequency = data.base.frequency || 1000;//default value
};
var waitForTimeout = function(data) {
return $.Deferred(function(dfrd) {
setTimeout(function() {
dfrd.resolve(data.endpoints);
}, data.base.frequency);
}).promise();
};
var callApi = function(endpoints) {
console.log('Calling API with given instructions ...');
return $.when.apply(null, endpoints.map(ep) {
return $.ajax({
type: ep.method,
dataType: ep.type,
url: ep.endpoint
}).then(null, function(jqXHR, textStatus, errorThrown) {
return textStatus;
});
}).then(function() {
//convert arguments to an array of results
return $.map(arguments, function(arg) {
return arg[0];
});
});
};
var handleResults = function(results) {
// results is an array of data values/objects returned by the ajax calls.
console.log("Handling data ...");
...
};
// The 'run' method
var run = function() {
getScenario()
.then(checkData)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(null, function(reason) {
console.error(reason);
})
.then(run);
};
return {
run : run
}
})(jQuery);
App.run();
This will stop on error but could be easily adapted to continue.
I'll try to answer your question using KrisKowal's q since I'm not very proficient with the promises generated by jQuery.
First of all I'm not sure whether you want to solve the array of promises in series or in parallel, in the solution proposed I resolved all of them in parallel :), to solve them in series I'd use Q's reduce
function getScenario() { ... }
function ajaxRequest(instruction) { ... }
function createPromisifiedInstruction(instruction) {
// delay with frequency, not sure why you want to do this :(
return Q.delay(instruction.frequency)
.then(function () {
return this.ajaxRequest(instruction);
});
}
function run() {
getScenario()
.then(function (data) {
var promises = [];
var instruction;
var i;
for (i = 0; i < data.endpoints.length; i += 1) {
instruction = {
method: data.endpoints[i].method,
type: data.endpoints[i].type,
endpoint: data.endpoints[i].endPoint,
frequency: data.base.frequency
};
promises.push(createPromisifiedInstruction(instruction));
}
// alternative Q.allSettled if all the promises don't need to
// be fulfilled (some of them might be rejected)
return Q.all(promises);
})
.then(function (instructionsResults) {
// instructions results is an array with the result of each
// promisified instruction
})
.then(run)
.done();
}
run();
Ok let me explain the solution above:
first of all assume that getScenario gets you the initial json you start with (actually returns a promise which is resolved with the json)
create the structure of each instruction
promisify each instruction, so that each one is actually a promise whose
resolution value will be the promise returned by ajaxRequest
ajaxRequest returns a promise whose resolution value is the result of the request, which also means that createPromisifiedInstruction resolution value will be the resolution value of ajaxRequest
Return a single promise with Q.all, what it actually does is fulfill itself when all the promises it was built with are resolved :), if one of them fails and you actually need to resolve the promise anyways use Q.allSettled
Do whatever you want with the resolution value of all the previous promises, note that instructionResults is an array holding the resolution value of each promise in the order they were declared
Reference: KrisKowal's Q
Try utilizing deferred.notify within setTimeout and Number(settings.frequency) * (1 + key) as setTimeout duration; msg at deferred.notify logged to console at deferred.progress callback , third function argument within .then following timeout
var App = (function ($) {
var getScenario = function () {
console.log("Getting scenario ...");
return $.get("http://demo3858327.mockable.io/scenario2");
};
var mapToInstruction = function (data) {
var res = $.map(data.endpoints, function(settings, key) {
return {
method:settings.method,
type:settings.type,
endpoint:settings.endPoint,
frequency:data.base.frequency
}
});
console.log("Instructions recieved:", res);
return res
};
var waitForTimeout = function(instruction) {
var res = $.when.apply(instruction,
$.map(instruction, function(settings, key) {
return new $.Deferred(function(dfd) {
setTimeout(function() {
dfd.notify("Waiting for "
+ settings.frequency
+ " ms")
.resolve(settings);
}, Number(settings.frequency) * (1 + key));
}).promise()
})
)
.then(function() {
return this
}, function(err) {
console.log("error", err)
}
, function(msg) {
console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n")
});
return res
};
var callApi = function(instruction) {
console.log("Calling API with given instructions ..."
, instruction);
var res = $.when.apply(instruction,
$.map(instruction, function(request, key) {
return request.then(function(settings) {
return $.ajax({
type: settings.method,
dataType: settings.type,
url: settings.endpoint
});
})
})
)
.then(function(data) {
return $.map(arguments, function(response, key) {
return response[0]
})
})
return res
};
var handleResults = function(data) {
console.log("Handling data ..."
, JSON.stringify(data, null, 4));
return data
};
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};
return {
// This will expose only the run method
// but will keep all other functions private
run : run
}
})($);
// ... And start the app
App.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
You have a return statement in the loop in your waitForTimeout function. This means that the function is going to return after the first iteration of the loop, and that is where you are going wrong.
You're also using the deferred antipattern and are using promises in places where you don't need them. You don't need to return a promise from a then handler unless there's something to await.
The key is that you need to map each of your instructions to a promise. Array#map is perfect for this. And please use a proper promise library, not jQuery promises (edit but if you absolutely must use jQuery promises...):
var App = (function ($) {
// Gets the scenario from the API
// NOTE: this returns a promise
var getScenario = function () {
console.log('Getting scenario ...');
return $.get('http://demo3858327.mockable.io/scenario');
};
// mapToInstructions is basically unnecessary. each instruction does
// not need its own timeout if they're all the same value, and you're not
// reshaping the original values in any significant way
// This wraps the setTimeout into a promise, again
// so we can chain it
var waitForTimeout = function(data) {
var d = $.Deferred();
setTimeout(function () {
d.resolve(data.endpoints);
}, data.base.frequency);
return d.promise();
};
var callApi = function(instruction) {
return $.ajax({
type: instruction.method,
dataType: instruction.type,
url: instruction.endPoint
});
};
// Final step: call the API from the
// provided instructions
var callApis = function(instructions) {
console.log(instructions);
console.log('Calling API with given instructions ...');
return $.when.apply($, instructions.map(callApi));
};
var handleResults = function() {
var data = Array.prototype.slice(arguments);
console.log("Handling data ...");
};
// The 'run' method
var run = function() {
getScenario()
.then(waitForTimeout)
.then(callApis)
.then(handleResults)
.then(run);
};
return {
run : run
}
})($);
App.run();
I would like to write a javascript function that returns informations from youtube videos; to be more specific I would like to get the ID and the length of videos got by a search, in a json object. So I took a look at the youtube API and I came out with this solution:
function getYoutubeDurationMap( query ){
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q="+ query +
"&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeMap = [];
$.getJSON(youtubeSearchReq, function(youtubeResult){
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
for(var i =0;i<youtubeResult.feed.entry.length;i++){
var youtubeVideoId = youtubeResult.feed.entry[i].id.$t.substring(27);
$.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2",function(videoDetails){
youtubeMap.push({id: videoDetails.entry.id.$t.substring(27),runtime: videoDetails.entry.media$group.media$content[0].duration});
});
}
});
return youtubeMap;
}
The logic is ok, but as many of you have already understood because of ajax when I call this function I get an empty array. Is there anyway to get the complete object? Should I use a Deferred object? Thanks for your answers.
Yes, you should use deferred objects.
The simplest approach here is to create an array into which you can store the jqXHR result of your inner $.getJSON() calls.
var def = [];
for (var i = 0; ...) {
def[i] = $.getJSON(...).done(function(videoDetails) {
... // extract and store in youtubeMap
});
}
and then at the end of the whole function, use $.when to create a new promise that will be resolved only when all of the inner calls have finished:
return $.when.apply($, def).then(function() {
return youtubeMap;
});
and then use .done to handle the result from your function:
getYoutubeDurationMap(query).done(function(map) {
// map contains your results
});
See http://jsfiddle.net/alnitak/8XQ4H/ for a demonstration using this YouTube API of how deferred objects allow you to completely separate the AJAX calls from the subsequent data processing for your "duration search".
The code is a little long, but reproduced here too. However whilst the code is longer than you might expect note that the generic functions herein are now reusable for any calls you might want to make to the YouTube API.
// generic search - some of the fields could be parameterised
function youtubeSearch(query) {
var url = 'https://gdata.youtube.com/feeds/api/videos';
return $.getJSON(url, {
q: query,
'max-results': 20,
duration: 'long', category: 'film', // parameters?
alt: 'json', v: 2
});
}
// get details for one YouTube vid
function youtubeDetails(id) {
var url = 'https://gdata.youtube.com/feeds/api/videos/' + id;
return $.getJSON(url, {
alt: 'json', v: 2
});
}
// get the details for *all* the vids returned by a search
function youtubeResultDetails(result) {
var details = [];
var def = result.feed.entry.map(function(entry, i) {
var id = entry.id.$t.substring(27);
return youtubeDetails(id).done(function(data) {
details[i] = data;
});
});
return $.when.apply($, def).then(function() {
return details;
});
}
// use deferred composition to do a search and then get all details
function youtubeSearchDetails(query) {
return youtubeSearch(query).then(youtubeResultDetails);
}
// this code (and _only_ this code) specific to your requirement to
// return an array of {id, duration}
function youtubeDetailsToDurationMap(details) {
return details.map(function(detail) {
return {
id: detail.entry.id.$t.substring(27),
duration: detail.entry.media$group.media$content[0].duration
}
});
}
// and calling it all together
youtubeSearchDetails("after earth").then(youtubeDetailsToDurationMap).done(function(map) {
// use map[i].id and .duration
});
As you have discovered, you can't return youtubeMap directly as it's not yet populated at the point of return. But you can return a Promise of a fully populated youtubeMap, which can be acted on with eg .done(), .fail() or .then().
function getYoutubeDurationMap(query) {
var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q=" + query + "&max-results=20&duration=long&category=film&alt=json&v=2";
var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
var youtubeMap = [];
var dfrd = $.Deferred();
var p = $.getJSON(youtubeSearchReq).done(function(youtubeResult) {
$.each(youtubeResult.feed.entry, function(i, entry) {
var youtubeVideoId = entry.id.$t.substring(27);
//Build a .then() chain to perform sequential queries
p = p.then(function() {
return $.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2").done(function(videoDetails) {
youtubeMap.push({
id: videoDetails.entry.id.$t.substring(27),
runtime: videoDetails.entry.media$group.media$content[0].duration
});
});
});
});
//Add a terminal .then() to resolve dfrd when all video queries are complete.
p.then(function() {
dfrd.resolve(query, youtubeMap);
});
});
return dfrd.promise();
}
And the call to getYoutubeDurationMap() would be of the following form :
getYoutubeDurationMap("....").done(function(query, map) {
alert("Query: " + query + "\nYouTube videos found: " + map.length);
});
Notes:
In practice, you would probably loop through map and display the .id and .runtime data.
Sequential queries is preferable to parallel queries as sequential is kinder to both client and server, and more likely to succeed.
Another valid approach would be to return an array of separate promises (one per video) and to respond to completion with $.when.apply(..), however the required data would be more awkward to extract.