I am trying to do a web app that uses the Spotify API (using node.js as server) but I've recently come across a problem I can't solve. Basically, I want to compare the content of two playlists but since the API only gives 100 tracks per request, I can't find a way to get this done. I already tried to use loops inside requests but I doesn't seem to work.
request.get(options, function(error, response, body) {
var data = [];
data.push(JSON.parse(JSON.stringify(body)));
let urls = [];
for (let i = 0; i < data[0].total; i = i + data[0].limit){
urls.push('https://api.spotify.com/v1/me/tracks?offset='+i);
}
data = [];
for (let i = 0; i < urls.length; i++) {
options.url = urls[i];
request.get(options, function(error, response, body) {
let tmp = JSON.parse(JSON.stringify(body));
let items = tmp.items
if (items != null){
for(let o of tmp.items) {
data.push(o.track.name);
}
if (tmp.next == null){
return res.send(JSON.stringify(data));
console.log('done');
}
}
});
}
You could use recursion to get all tracks in a playlist.
You could have a method that gets the playlist page then loop call it until you don't have any more tracks to retrieve. Just as an example, here is a possible solution in pseudo code. I've written this quickly so forgive any bugs...
function getplaylisttracks(playlistid, pagesize,offset) {
//call 'get tracks' api with pagesize and offset
//return response
}
var offset = 0;
var pagesize = 100;
var continueloop = true;
var result = getplaylisttracks(YOURID, pagesize, offset);
do {
try {
//*** process result tracks into your array here ***
if(result.next!=null) {
offset = offset+pagesize;
result = getplaylisttracks(YOURID, pagesize, offset);
}
else {
continueloop = false;
}
}
catch(e) {
//handle error here...
continueloop = false;
}
}
while(continueloop);
Once you have all the tracks for each playlist you could run a comparison
Related
I have a callable function that should return a value, but the only thing ever returned is null. Below is the current version of the function. I have also tried having a return on the first promise (the original once call), and at the end in another then returning the GUID. It actually returned data in that case, but it returned immediately and the GUID was empty.
How can I accomplish my goal and still return the GUID? I don't know when the function is called if I will use a new GUID that I generate, or one that already exists in the database.
There is a similar question here: Receiving returned data from firebase callable functions , but in that case it was because he never returned a promise from the function. I am returning a promise on all code paths. Unless I have to return the initial promise from the once call? In which case, how can I return the GUID when I don't know it yet?
I am also trying to throw an error in a couple of places and the error shows up in the logs for the function, but is never sent to the client that called the function.
I am going off of the examples here: https://firebase.google.com/docs/functions/callable
Sorry for the code bomb.
Calling the function:
var newGame = firebase.functions().httpsCallable('findCreateGame');
newGame({}).then(function(result) {
// Read result of the Cloud Function.
//var sGameID = result.data.guid;
console.log(result);
}).catch(function(error) {
console.log(error);
});
Function:
exports.findCreateGame = functions.https.onCall((data, context) => {
console.log("findCurrentGame Called.")
/**
* WHAT NEEDS DONE
*
*
* Pull in user's information
* Determine their win/loss ratio and search for a game using transactions in either low medium or high queue
* If there are no open games in their bracket, search the one above, then below
* If no open games anywhere, create a new game in their bracket
* If an open game is found, write the UID to the game and add the game's ID to the user's profile
*
*/
var uid = context.auth.uid;
var section = "";
var sUsername = "";
var sProfilePic = "";
var currentGames = null;
var sGUID = "";
//Get the user's info
var userref = admin.database().ref('users/' + uid);
userref.once("value", function(data) {
var ratio = 0;
var wins = parseInt(data.val().wins);
var losses = parseInt(data.val().losses);
var lives = parseInt(data.val().lives);
if (lives < 1){
//This user is out of lives, should not have been able to get here
//Throw an exception so that we can see why it failed
throw new functions.https.HttpsError('permission-denied', 'You do not have enough lives to start a new game.');
}
sUsername = data.val().username;
sProfilePic = data.val().profilepicture;
//Handle if they have no losses
if (losses == 0){
ratio = 100;
} else {
ratio = (wins / losses) * 100;
}
//If they have played less than 5 games, put them in noob tier
if (wins + losses < 5){
ratio = 0;
}
if (ratio <= 33){
section = "noob";
} else if (ratio > 33 && ratio <= 66){
section = "average";
} else {
section = "expert";
}
}).then(() => {
//Get all of the games this user is currently in
admin.database().ref('games').orderByChild(uid).once('value', function(data) {
currentGames = data.val();
}).then(() => {
//Generate a new GUID in case we need to set up a new game
sGUID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
var queueref = admin.database().ref('gamequeue/' + section);
queueref.transaction(function(currentGUID) {
if (currentGUID == null){
//Write our GUID in the queue
return sGUID;
} else {
//Get the id of the game we just got
sGUID = currentGUID
return null;
}
}).then((res) => {
if (res.snapshot.val() != null){
//This means we are creating the game lobby
//Generate a new answer
var newAnswer = "";
while (newAnswer.length < 4){
var temp = Math.floor(Math.random() * 9) + 1;
temp = temp.toString();
if (!newAnswer.includes(temp)){
newAnswer += temp;
}
}
var obj = {username: sUsername, score: 0, profilepicture: sProfilePic};
return admin.database().ref('games/' + sGUID).set({id: sGUID, requestor: uid, [uid]: obj, answer: newAnswer, turn: uid, status: 'pending'}).then(() => {
return {guid: sGUID};
});
} else {
//We found a game to join
//If we are in a duplicate request situation, make sure the GUID is a string
if (typeof(sGUID) != 'string'){
sGUID = Object.keys(sGUID)[0];
}
//Make sure we didn't find our own game request
if (currentGames[sGUID] != null){
//Add this GUID back to the queue, we shouldn't have removed it
return admin.database().ref('gamequeue/' + section + '/' + sGUID).set('');
//Throw an exception that says you can only have one open game at a time
throw new functions.https.HttpsError('already-exists', 'We are still finding a match for your last request. You are only allowed one open request at a time.');
} else {
//Get the current game info
admin.database().ref('games/' + sGUID).once('value', function(data) {
var sRequestor = data.val().requestor;
var sOpponentUsername = data.val()[sRequestor].username;
var sOpponentProfilePic = data.val()[sRequestor].profilepicture;
//Write all of our info to the game
return admin.database().ref('games/' + sGUID).update({[sRequestor]: {opponentusername: sUsername, opponentprofilepicture: sProfilePic}, [uid]: {username: sUsername, score: 0, opponentusername: sOpponentUsername, opponentprofilepicture: sOpponentProfilePic}, status: 'active'}).then(() => {
return {guid: sGUID};
});
});
}
}
});
});
})
});
The documentation for callable functions explains:
To return data after an asynchronous operation, return a promise. The
data returned by the promise is sent back to the client.
You have many asynchronous operations that must be chained together. A return needs to be added to each of these statements (as shown):
return userref.once("value", function(data) {...
return admin.database().ref('games').orderByChild(uid).once('value', function(data) {...
return queueref.transaction(function(currentGUID) {...
return admin.database().ref('games/' + sGUID).once('value', function(data) {...
I am trying to do sync call using tableau.
var checkLen = $s_filter_i.length;
for (i = 0; i < checkLen; i++) {
var filterValfscr = $s_filter_i[i].VALUE;
Send_Tablo(filterValfscr);
}
When call this function Send_Tablo(filterValfscr) it runs the below code.
But the problem is before I get response form getSummaryDataAsync() it comes out of the function recalls Send_Tablo(filterValfscr) again I get the latest request data instead of first data request.
function Send_Tablo() {
var filter_indx = JSON.parse(window.localStorage.getItem('filter_indx'));
var arry = [];
for (var i = 0; i < filter_indx.s_filter_i.length; i++) {
Attr_lab_name = filter_indx.s_filter_i[i].ATTRIBUTELABEL;
arry.push(filter_indx.s_filter_i[i].VALUE);
}
currentViz.getWorkbook().changeParameterValueAsync('Attribute_Label', filterValfscr, Attr_lab_name, arry);
alert(filterValfscr);
var fScr_data;
sheet = currentViz.getWorkbook().getActiveSheet();
sheet.getSummaryDataAsync(options).then(function (t) {
data = t.getData();
console.log("Data", data);
frscr_demo();
});
function frscr_demo() {
//var fScr_data;
currentViz.getWorkbook().getActiveSheet().applyFilterAsync(Attr_lab_name, arry, tableau.FilterUpdateType.REPLACE);
sheet = currentViz.getWorkbook().getActiveSheet();
sheet.getSummaryDataAsync(options).then(function (t) {
fScr_data = t.getData();
console.log("FSCR", fScr_data);
var aa = $(fScr_data).length;
});
}
}
What I am try to achieve is Send_Tablo() should run all the Async function first before running the second iteration of Send_Tablo() from the for loop.
Do let me known what I am doing wrong? Thanks in advance.
I was following this tutorial when a wild step 9 appears.
This problem is the same as the previous problem (HTTP COLLECT) in that you need to use http.get(). However, this time you will be provided with three URLs as the first three command-line arguments.
You must collect the complete content provided to you by each of the URLs and print it to the console (stdout). You don't need to print out the length, just the data as a String; one line per URL. The catch is that you must print them out in the same order as the URLs are provided to you as command-line arguments.
My code was (It doesn't work fine just when he pleases):
http = require("http");
var url = [process.argv[2], process.argv[3], process.argv[4]];
var responses = [];
var completed_responses = 0;
for(var i in url){
http.get(url[i], function(response){
var content = "";
//if(completed_responses == url.length){
response.setEncoding("utf-8");
response.on("data", function(data){
content += data;
})
response.on("error", console.error);
response.on("end", function(end){
console.log(content);
});
})
}
And the answer was:
var http = require("http");
var bl = require("bl");
var results = [];
var count = 0;
function printResults(){
for(var i = 0; i < 3; i++)
console.log(results[i]);
}
function httpGet(index){
http.get(process.argv[2 + index], function(response){
response.pipe(bl(function(err, data){
if (err)
return console.error(err);
results[index] = data.toString();
count++;
if(count == 3)
printResults()
}))
})
}
for(var i = 0; i < 3; i++)
httpGet(i);
What is the right answer WITHOUT BL/AFTER/ETC?
Thanks to all!
I've done that tutorial myself when I was first learning node and I remember that step of the tutorial. The solution was fairly underwhelming. Anyway, for your answer:
NodeJs Asynchronous programming - Coordinating parallel calls
You can check the code in the question and make the fixes I suggested in my answer. That should solve it without BL/Async/Whatever else that tutorial mentions.
Here is my code for the Juggling Async challenge without using any third-party libraries.
var http = require("http");
var urls = [process.argv[2], process.argv[3], process.argv[4]];
var urlResults = new Array("", "", "");
var allDoneCount = 0;
urls.forEach(function (_url) {
http.get(_url, function (resp) {
resp.on("data", function (data) {
if (_url === urls[0]) {
urlResults[0] += data.toString();
} else if (_url === urls[1]) {
urlResults[1] += data.toString();
} else {
urlResults[2] += data.toString();
}
})
resp.on("end", function () {
allDoneCount++;
if (allDoneCount === 3) {
console.log(urlResults[0]);
console.log(urlResults[1]);
console.log(urlResults[2]);
}
})
resp.on("error", function (err) {
console.log(err);
})
}).on("error", function (err) {
console.log(err);
})
})
This is how you can do it without any external modules(except http ;P).
const http = require('http'); //http module
let results = ["", "", ""]; //this will store the data from http.get()
let counter = 0; //to keep a counter for no of httpget's done
//it will iterate when counter is 3 i.e. the 'end' for all
function print() {
for (let i = 0; i < 3; i++) {
console.log(results[i]);
}
}
//accept index(for process.argv) as parameter
function httpGetter(i) {
//http.get method on the url first encountered, 2+i because 2 values are reserved
http.get(process.argv[2 + i], (res) => {
//for converting (res)ponse to string/alternatively toString() method can be used
res.setEncoding('utf8');
//event data on the url, callback with recived chunk as parameter
res.on('data', function(chunk) {
//appending the recived chunk to that element of results corresponding to 'i' of httpGetter function
results[i] += chunk;
});
//event end, when no more data is read
//runs every time for each value of 'i' that is for each url
res.on('end', function() {
//to keep count
counter++;
//when 3 that is when data from all inputs receved
if (counter === 3) {
//print function simply iterating over results array
print();
}
});
})
}
//inputs are recieved from here
for (let i = 0; i < 3; i++) {
//i can be index for results
httpGetter(i);
}
I am reasonably new to node.js / sails.js and have run into a problem that I know the answer is simple but I cannot seem to work it out.
My code is as follows
SendCompleted : function(req,res)
{
var updated = 0;
var params = req.params.all();
var dt = JSON.parse(decodeURI(params.id));
var connection = new sql.Connection(testrmis, function (err)
{
if (err) {
}
for(var i = 0; i < dt.length; i++) {
var obj = dt[i];
var request = new sql.Request(connection);
request.stream = true;
request.input('branchid', sql.Int(), obj.branch_id);
request.input('picklistid', sql.Int(), obj.picklist_id);
request.input('scanned',sql.Int(),obj.scanned);
request.input('expected', sql.Int(),obj.expected);
request.input('poscode', sql.VarChar(),obj.poscode);
request.input('label', sql.VarChar(), obj.label);
request.input('dt', sql.VarChar(), obj.dt);
request.execute('WAREHOUSE_InsertPiPackData');
request.on('done', function(returnValue) {
updated = updated + returnValue;
console.log(updated);
});
}
res.send("[{\"ReturnValue\":" + updated + "}]");
});
}
I am sending in 4 lines of results and my console.log(updated) counts up as it should for each line, e.g 1,2,3,4
However the res.send result for updated is always 0.
Could anyone please explain why this is happening? My var updated is outside of my loop and this is getting updated correctly, however when the loop is finished it seems to get reset to 0?
returnValue == ##rowcount from the stored procedure
request is async so
res.send("[{\"ReturnValue\":" + updated + "}]");
gets executed even before you get the callback on request as JS doesn't wait for the callback and executes the next line. What you can do is use a counter and place your res.send inside for loop.
SendCompleted : function(req,res)
{
var updated = 0;
var params = req.params.all();
var dt = JSON.parse(decodeURI(params.id));
var connection = new sql.Connection(testrmis, function (err)
{
if (err) {
}
var count = dt.length;
for(var i = 0; i < dt.length; i++) {
var obj = dt[i];
var request = new sql.Request(connection);
request.stream = true;
request.input('branchid', sql.Int(), obj.branch_id);
request.input('picklistid', sql.Int(), obj.picklist_id);
request.input('scanned',sql.Int(),obj.scanned);
request.input('expected', sql.Int(),obj.expected);
request.input('poscode', sql.VarChar(),obj.poscode);
request.input('label', sql.VarChar(), obj.label);
request.input('dt', sql.VarChar(), obj.dt);
request.execute('WAREHOUSE_InsertPiPackData');
request.on('done', function(returnValue) {
count--;
updated = updated + returnValue;
console.log(updated);
if(count == 0) res.send("[{\"ReturnValue\":" + updated + "}]");
});
}
});
}
Try for this:
May be Async problem:
for(var i = 0; i < dt.length; i++) {
//Your logic
if(i=== dt.length){
res.send("[{\"ReturnValue\":" + updated + "}]");
}
}
This is because at the time you do request.send, the value of updated is not incremented. This is because request.execute is asynchronous and done handler will be invoked after the res.send has been executed.
I would recommend a promise library (example, q). You can combine the promises and then use Q.all to do req.send when all the promises are done.
See more details here
I have a question about the http request.
Here is my old post.
How to get the multiple http request results in my example?
I have modified my codes a bit. Basically I need to make multiple http requests and store them into an productGroup array. However, I am getting undefined for the returned result.
var buildProduct = function(product) {
var productGroup = [];
for(var i = 0; i < product.length; i++) {
var t = buildProductDetail(product, i)
productGroup.push(t);
}
console.log(productGroup) // I am getting undefined here.
return productGroup;
}
var buildProductDetail = function(product, i) {
var plan = {}
getProductDetail(product[i].id)
.then(function(data){
plan = {detail: data.detail, name:product[i].name}
console.log(plan) //has data
return plan;
})
}
var getProductDetail = function(id) {
return $http.get('/api/project/getProduct' + id);
}
You had undefined because your buildProductDetail function didn't return anything.
If you want a clean result use the $q api to resolve several promises at the same time.
https://docs.angularjs.org/api/ng/service/$q
I think it should work with something looking like this but I can't test without a plunkr.
Inject $q (native in angularjs, no external dep needed) and then :
var buildProduct = function(product) {
var productGroup = [];
for(var i = 0; i < product.length; i++) {
var t = buildProductDetail(product, i)
productGroup.push(t);
}
return $q.all( productGroup );
}
var buildProductDetail = function(product, i) {
var plan = {}
return getProductDetail(product[i].id) // don't forget the return there
.then(function(data){
plan = {detail: data.detail, name:product[i].name}
console.log(plan) //has data
return plan;
})
}