I'm getting stuck trying to figure out how to fill this 'artists' array with consecutive calls to the spotify api. I'm trying to fill the 'artists' array with every artist in the user's saved library. The way the call works is, it will return a chunk of max 50 artists per call. However when I fill the array here and then try to pass a new i value to the getUserData function, I don't think the i value is updated and for some reason my 'artists' array is filling up to only 226 while the 'total' value is 276 for my test case. Can someone help me with this? I think it may be a scope issue but I can't figure out where. Thank you in advance!
var i = 0;
function getUserData(accessToken, i) {
return $.ajax({
url: 'https://api.spotify.com/v1/me/tracks?limit=50&offset=' + i,
headers: {
'Authorization': 'Bearer ' + accessToken
}
});
}
var artists = [];
loginButton.addEventListener('click', function() {
login(function(accessToken) {
getUserData(accessToken, i)
.then(function(response) {
var total = response.total;
while(i < total) {
i+=50;
getUserData(accessToken, i)
.then(function(response) {
total = response.total;
alert(total); //testing
loginButton.style.display = 'none';
for (var j = 0; j < 50 && artists.length < total; j++){
artists.push(response.items[j].track.album.artists[0].name);
}
alert(artists[7] + artists.length); //testing
alert(artists[artists.length - 1]); //testing
});
}
});
});
});
Thank you for your help!
your first getUserData in the while loop starts at i = 50 - so the first 50 wont be pushed into artists array
add
for (var j = 0; j < 50 && artists.length < total; j++) {
artists.push(response.items[j].track.album.artists[0].name);
}
before the while loop - like so
var artists = [];
loginButton.addEventListener('click', function() {
login(function(accessToken) {
var i = 0;
getUserData(accessToken, i)
.then(function(response) {
var total = response.total;
// added code to store the first 50 artists
for (var j = 0; j < 50 && artists.length < total; j++) {
artists.push(response.items[j].track.album.artists[0].name);
}
// end added code
while (i < total) {
i += 50;
getUserData(accessToken, i)
.then(function(response) {
total = response.total;
alert(total); //testing
loginButton.style.display = 'none';
for (var j = 0; j < 50 && artists.length < total; j++) {
artists.push(response.items[j].track.album.artists[0].name);
}
alert(artists[7] + artists.length); //testing
alert(artists[artists.length - 1]); //testing
});
}
});
});
});
The only issue with this is that the order of artists could conceivably be "wrong", in that one of the asynchronous getUserData could take longer than the next one (network issues are unpredictable)
You can rewrite your code to use Array#map, Array#concat and $.when - and preserve the order of data by doing so
I'm assuming response.total is the total number of tracks, regardless of the offset and limit as this is implied by your code
loginButton.addEventListener('click', function() {
login(function(accessToken) {
loginButton.style.display = 'none';
var arr = [getUserData(accessToken, i)];
arr[0]
.then(function(response) {
for (var i = 50; i < response.total; i += 50) {
arr.push(getUserData(accessToken, i));
}
$.when.apply($, arr)
.then(function() {
var artists = [].concat.apply([], [].map.call(arguments, function(response) {
return response.items.map(function(item) {
return item.track.album.artists[0].name;
});
}));
// artists array is filled, now do what you need
});
})
.catch(function(err) {
// handle errors here
});
});
});
As above but using native Promise.all
loginButton.addEventListener('click', function() {
login(function(accessToken) {
loginButton.style.display = 'none';
var arr = [getUserData(accessToken, i)];
arr[0]
.then(function(response) {
for (var i = 50; i < response.total; i += 50) {
arr.push(getUserData(accessToken, i));
}
Promise.all(arr).then(function(chunks) {
var artists = [].concat.apply([], chunks.map(function(response) {
return response.items.map(function(item) {
return item.track.album.artists[0].name;
});
}));
// artists array is filled, now do what you need
});
})
.catch(function(err) {
// handle errors here
});
});
});
Related
I'm automating the completion of a questionnaire via Cypress using fixtures. I need to create a new IT statement on each iteration of a loop running as currently if 1 set fails in the loop, then the rest of the dataset does not run.
Any help would be appreciated.
context('Questionnaire Completion', () => {
Cypress.Cookies.debug(true);
beforeEach(function() {
cy.fixture('data_set_4').then((testdata) => {
this.data = testdata
});
});
describe ('I will complete the form with each entry in the data set', function() {
it ('I will complete the form with each entry in the data set', function() {
for (let i = 0; i < this.data.testdata.length ; i++) {
participantcreation(this.data.testdata[i].DataEntry);
//I will enter a name and click Next
Name(this.data.testdata[i].DataEntry);
for (let j = 1; j < 31 ; j++) {
let answer = this.data.testdata[i]['Q' + j] - 1;
cy.get('#questionnaire_question_'+j+'_'+answer).click({ force: true });
}
});
});
});
Not entirely sure about what is being asked here but if you wish to create multiple 'its' depending on what you need you can just put it in a loop
describe('Tests', () => {
var i;
for (i = 0; i < 5; i++) {
it(`Test ${i}`, () => {
cy.wait(200)
});
}
});
I am trying to use $.when apply in my code. However, it seems that the format return is different for single and multiple request. How can i cater for it?? I am trying not to have another if else outside of it.
$.when.apply(null, apiRequestList).then(function () {
for (var i = 0; i < arguments.length; i++) {
var value = arguments[0];
}
});
This is what i do not want to do.
if (apiRequestList.length === 1) {
$.ajax({
});
} else {
$.when.apply(null, apiRequestList).then(function () {
for (var i = 0; i < arguments.length; i++) {
var value = arguments[0];
}
});
}
You can simply convert arguments into an array, when the length of apiRequestList is 1:
$.when.apply(null, apiRequestList).then(function() {
var _arguments = Array.prototype.slice.call(arguments);
if (Array.isArray(apiRequestList) && apiRequestList.length === 1)
_arguments = [arguments];
for (var i = 0; i < _arguments.length; i++) {
var value = _arguments[i][0];
console.log(value);
}
});
Live Example on jsFiddle (since we can't do ajax on Stack Snippets):
function x(a) {
return $.post("/echo/html/", {
html: "a = " + a,
delay: Math.random()
});
}
function doIt(apiRequestList) {
$.when.apply(null, apiRequestList).then(function() {
var _arguments = arguments;
if (Array.isArray(apiRequestList) && apiRequestList.length === 1)
_arguments = [arguments];
for (var i = 0; i < _arguments.length; i++) {
var value = _arguments[i][0];
console.log(value);
}
console.log("----");
});
}
doIt([x(1), x(2), x(3)]);
doIt([x(4)]);
Example output (it'll vary because of the Math.random()):
a = 4
----
a = 1
a = 2
a = 3
----
I am making 3 get JSON calls in myFunction() that gets called on a button click. 2 of these getJSONs depend on each other for their execution. It basically parses through 10 web pages and collects some data. With this data it will go to another page and collect some other data. I want to display "DONE" at the end of myFunction so the user knows that we have finally got all data and the search operation is complete. However these calls, I think, are asynchronous, so I use deferred objects. But even though I pass all calls to $.when.apply(call1,call2,call3), it displays the "DONE" before any data is printed on the console. Once it prints "DONE", then it starts to print the results. How can I modify my code so that I would be able to display "DONE" only when myFunction has ran completely for all 10 pages and has printed all data on the console.
var call1 = [];
var call2 = [];
var call3 = [];
function myFunction() {
data3 = [];
url = ''; // some URL here
call3.push($.getJSON(url, function(data4) {
data3 = data4;
}));
for (var page = 1; page < 10; page++) {
(function(page) {
url = 'http://example.com/' + page;
call1.push($.getJSON(url, function(data1) {
for (var num = 0; num < data1.standings.results.length; num++) {
(function(num) {
url = 'http://example.com/' + data1.entry[num];
call2.push($.getJSON(url, function(data2) {
for (var i = 0; i < 15; i++) {
(function(i) {
console.log(data3.ele[(data2.p[i].element) - 1].x);
return;
}
})(i);
}
})
);
})(num);
}
})
);
})(page);
};
$.when.apply(call1, call2, call3).then(function() {
console.log("DONE");
});
}
I was finally able to solve this problem. As mentioned in the comments, we need to chain various promises and the function structure should match the structure of the when command. So, in the function as call1 was pushed first, I need to call the when command for call1 and then nest the subsequent commands and so on.
var call1 = [];
var call2 = [];
var call3 = [];
function myFunction() {
data3 = [];
url = ''; // some URL here
call3.push($.getJSON(url, function(data4) {
data3 = data4;
}));
for (var page = 1; page < 10; page++) {
(function(page) {
url = 'http://example.com/' + page;
call1.push($.getJSON(url, function(data1) {
for (var num = 0; num < data1.standings.results.length; num++) {
(function(num) {
url = 'http://example.com/' + data1.entry[num];
call2.push($.getJSON(url, function(data2) {
for (var i = 0; i < 15; i++) {
(function(i) {
console.log(data3.ele[(data2.p[i].element) - 1].x);
return;
}
})(i);
}
})
);
})(num);
}
})
);
})(page);
};
$.when.apply($, call1).then(function(){
$.when.apply($, call2).then(function(){
document.getElementsByName('output')[0].value+="Search Completed"+'\r\n';
});
});
}
I have a angular service inside a for loop that returns an array of object. I want to get the summation of the value returned by that service but I got nothing in the end. My service works fine but my problem is I can't get the summation. Below is my code
controller
var TotalOBValueLand = 0;
for(var i = 0; i < $scope.selectedProp.length; i++){
AccountService.getTopAccountDetails($scope.selectedProp[i]["propId"]).then(function(msg){
TotalOBValueLand += parseInt(msg.data[0].OBValueLand);
//my return data here has no error.
});
}
console.log(TotalOBValueLand); //I got zero;
Use Promise.all and array#map to get an array of results, then use Array#reduce to sum them up
var TotalOBValueLand = 0;
Promise.all($scope.selectedProp.map(function(prop) {
return AccountService.getTopAccountDetails(prop).then(function(msg){
return parseInt(msg.data[0].OBValueLand);
});
})).then(function(results) {
TotalOBValueLand = results.reduce(function(a, b) {
return a + b;
});
console.log(TotalOBValueLand);
});
In response to the comments
var TotalOBValueLand = 0;
var TotalOBValueBuilding = 0;
Promise.all($scope.selectedProp.map(function(prop) {
return AccountService.getTopAccountDetails(prop).then(function(msg){
return parseInt(msg.data[0]);
});
})).then(function(results) {
TotalOBValueLand = results.reduce(function(a, b) {
return a.OBValueLand + b.OBValueLand;
});
TotalOBValueBuilding = results.reduce(function(a, b) {
return a.OBValueBuilding + b.OBValueBuilding ;
});
console.log(TotalOBValueLand, TotalOBValueBuilding);
});
and a little more generic
Promise.all($scope.selectedProp.map(function(prop) {
return AccountService.getTopAccountDetails(prop).then(function(msg){
return parseInt(msg.data[0]);
});
})).then(function(results) {
var totals = results.reduce(function(result, a) {
Object.keys(a).forEach(function(key) {
result[key] = (result[key] || 0) + a[key];
});
return result;
}, {});
console.log(totals.OBValueLand, totals.OBValueBuilding);
});
You cannot access console.log(TotalOBValueLand); outside the response since .getTopAccountDetails() is asynchronous, it will be always 0.
try to wrap it inside,
var TotalOBValueLand = 0;
for(var i = 0; i < $scope.selectedProp.length; i++){
AccountService.getTopAccountDetails($scope.selectedProp[i]["propId"]).then(function(msg){
TotalOBValueLand += parseInt(msg.data[0].OBValueLand);
console.log(TotalOBValueLand);
});
}
The problem is that you are mixing asynchronous and synchronous functions. This should demonstrate what is going on a little for you
https://jsfiddle.net/Austio/v7goqk4d/
AccountService = {
getTopAccountDetails: function() {
return new Promise((resolve) => resolve(1))
}
}
var TotalOBValueLand = 0;
for(var i = 0; i < 2; i++){
AccountService.getTopAccountDetails().then(function(x){
TotalOBValueLand += x;
console.log('incremented async', TotalOBValueLand)
});
}
console.log('sync', TotalOBValueLand);
setTimeout(() =>
console.log('timeout', TotalOBValueLand), 2000)
Solution using an array of promises that we resolve
var TotalOBValueLand = 0;
promises = []
for(var i = 0; i < 2; i++){
promise = AccountService
.getTopAccountDetails()
promises.push(promise)
}
console.log('before', TotalOBValueLand);
Promise
.all(promises)
.then(results => {
TotalOBValueLand = results.reduce((curr,acc) => curr + acc, 0);
console.log('done', TotalOBValueLand);
return TotalOBValueLand;
})
.catch(err => 'handle me')
So, I have been trying for the past few hours to get an result out of a function after performing some for loops :
Cluster.prototype.initiate_api_data_fetching = function(username) {
var self = this,
object = [];
return self.initiate_available_market_search(username, function(data_object){
var json_obj = JSON.parse(data_object);
for(var obj_key in json_obj) {
for (var i = json_obj[obj_key].length - 1; i >= 0; i--) {
self.initiate_market_items_data_fetching(username, json_obj[obj_key][i].site, function(data_obj){
var json_object = JSON.parse(data_obj);
for(var data_key in json_object) {
for (var j = json_object[data_key].length - 1; j >= 0; j--) {
object.push(json_object[data_key][j]);
/*log(object);*/
};
};
log(object);
});
};
};
});
};
Making abstraction of all the variables and other things that make no sense to you readers, I would just like to know how can I return the object array with the data that I\m pushing in it. Everything is fine if I\m logging where the /*log(object);*/ is, but if I want to see what the object contains at the end of the function, I get an empty array.
I suggest you add a callback to your main function and call it when done..
Cluster.prototype.initiate_api_data_fetching = function (username, callback) {
var self = this,
object = [];
return self.initiate_available_market_search(username, function (data_object) {
var json_obj = JSON.parse(data_object)
, counter = 0;
function done() {
counter -= 1;
if (counter === 0) {
callback(object);
}
}
for (var obj_key in json_obj) {
if (!json_obj.hasOwnProperty(obj_key)) { continue; }
for (var i = json_obj[obj_key].length - 1; i >= 0; i--) {
counter += 1;
self.initiate_market_items_data_fetching(username, json_obj[obj_key][i].site, function (data_obj) {
var json_object = JSON.parse(data_obj);
for (var data_key in json_object) {
if (!json_object.hasOwnProperty(data_key)) { continue; }
for (var j = json_object[data_key].length - 1; j >= 0; j--) {
object.push(json_object[data_key][j]);
/*log(object);*/
}
}
done();
});
}
}
});
};
PS. 1 assumption is that initiate_api_data_fetching is async.
PS. 2 Follow the advice from the commenters above to improve your code. I answered your immediate question by showing you how to synchronise async calls, but don't stop there.