I'm not sure this is the simplest solution but it stands a chance of working.
var ajax_always_promises = [],//to be populated with promises that (barring uncaught error) are guaranteed to be resolved.
data_arr = [],//an array to be (sparsely) populated with asynchronously delivered json data.
error_arr = [];//an array to be (sparsely) populated with ajax error messages.
$.each(window.ids, function(i, id) {
var dfrd = $.Deferred();
var p = $.ajax({
url: 'api/get_runs_stats.php',
type: 'GET',
dataType: 'json',
data: {
run_id: window.ids[i]
}
}).done(function(json_data) {
data_arr[i] = json_data;//intentionally not `data_arr.push(json_data);`
}).fail(function(jqXHR, textStatus, errorThrown) {
error_arr[i] = textStatus;//intentionally not `error_arr.push(textStatus);`
}).always(dfrd.resolve);
ajax_always_promises[i] = dfrd.promise();
doneDefers(p);
});
$.when.apply(null, ajax_always_promises).done(function() {
//The data in the (sparsely) populated arrays `data_arr` and `error_arr` is available to be used.
var i, id, success_count=0, error_count=0;
for(i=0; i<Math.max(data_arr.length,error_arr.length); i++) {
//Here, the index i corresponds to the original index of window.ids ...
//...that's the advantage of sparsely populating the arrays.
id = window.ids[i];
if(data_arr[i]) {
//Here, do whatever is required with `data_arr[i]`, and `id` if needed.
success_count++;
}
else if(error_arr[i]) {
//Here, do whatever is required with `error_arr[i]`, and `id` if needed.
error_count++;
}
}
console.log("Success:errors: " + success_count + ':' + error_count);
});
Untested - may well need debugging