How to pass variable reference to Observable subscription - javascript

I'm trying to get tweets from Twitter, and then get the hashtags from those tweets and get images from Flickr.
I want the tweets and images to be united together. However, see the console logs at the end. I expect the first one will output the current tweet and the second will output the images retrieved for this tweet.
However, what happens is that console.log(tweets[i]) always prints out the last tweet in the list, while console.log(results) prints the current results (i.e. every flickr result is printed).
By the way, the tweets and flicks are being retrieved from a json file for now.
tweets$.subscribe((tweets) => {
for (var i in tweets) {
var hashtags = tweets[i].entities.hashtags;
for (var j in hashtags) {
var flicks$ = this.flickrService.getImagesMock(hashtag[j]);
flicks$.subscribe((results) => {
console.log(tweets[i]);
console.log(results);
});
}
}
});
So my question is, how do I get the tweets[i] in the $flicks.subscribe to refer to the i that was in use when the subscription was created?

I guess it's a clasical problem with scope in async js.
for (var i in tweets) {
(function(index) {
var hashtags = tweets[index].entities.hashtags;
for (var j in hashtags) {
var flicks$ = this.flickrService.getImagesMock(hashtag[j]);
flicks$.subscribe((results) => {
console.log(tweets[index]);
console.log(results);
});
}
})(i);
}
Basically, in your example nested subscribe is executed after first loop is already finished.

Related

Retrieving data from an object only works in the original function

I'm having issues retrieving data from an object using JS on my website. I have a third party scrape Instagram posts and provides JSON to my website via a link. I've managed to retrieve this data from the link and manipulate it, but the problem comes when I try to change the displayed image every 5 seconds.
I took the solution from How to change an image every 5 seconds for example? and tried to adapt for my solution, however, I get an error where posts[index] is undefined even though it shouldn't be.
posts = [];
let index = 0;
indexx = 0
$.getJSON('posts.json', function(data) {
$.each(data, function(i, f) {
posts[indexx] = f
indexx = indexx + 1
});
});
console.log(posts) // returns all the posts
window.onload = change();
function change() {
console.log(posts) // Returns the list of posts
console.log(posts[index]) // Returns 'undefined'
console.log(posts[1]) // Returns 'undefined'
$('#instaimg').attr('src', posts[index]["mediaUrl"])
if (index == 5) {
index = 0;
} else {
index++;
}
setTimeout(change, 5000);
}
I'm not sure if I am missing something or whether my lack of JS knowledge is to blame, but if anyone could help it would be appreciated
Several issues with your code:
Your console.log(posts) will show an empty array because the ajax callback has not finished yet => move that inside the .getJSON callback function
You call change recursively every 5 sec, e.g your call stack will grow indefinitely
Use setInterval instead of setTimeout
Start the interval timer inside the .getJSON callback function, so that it starts once the fetched data is ready
Use .push() to add to an array, no need to keep track of the index
Use $(function() { to make sure the DOM is ready before you do any action
You use a hardcoded length 4 for your data length, reference the array size instead
Updated code:
let index = 0;
let posts = [];
$(function() {
$.getJSON('posts.json', function(data) {
//$.each(data, function(i, f) {
// posts.push(f);
//});
// It looks like data is the array you want to use, so:
posts = data;
setInterval(changeImage, 5000);
});
});
function changeImage() {
$('#instaimg').attr('src', posts[index++]["mediaUrl"]);
if(index > posts.length) {
index = 0;
}
}

How to "control" when Firestore.getAll(...promises) stops by reject

Context:
I 'm doing a cloud function to send pushes to multiple users. I need to recover the info of each user to know some data like, name, country..etc..
Problem:
Actually I recover the list of user Id's and when I got it, then I create an array of promisesto recover all the info:
var usersPromises = []
for (var i = 0; i < usersInRange.length; i++) {
usersPromises[i] = firestore.collection("users").doc(usersInRange[i])
}
Then I recover and send the push using firestore.getAll():
firestore.getAll(...usersPromises).then(results => {
for(snapshot in results){
if(snapshot.exists){
......
var user = snapshot.data()
......
}else{
......
}
}
})
This solution is actually working "fine" almost all the time. But at this moment the Firestore db has some users that do not exist or something is wrong, because the method getAll()stops before finishing all the promises. I know it because no push is sent, and in the console, just say that the method has finished.
Reading in SO and documentation, I saw, that getAll stops if some promise is "broken". (all or nothing)
And here is where I'm lost. How can I "force" or do in another way, to just "jump" this promises that can't be completed?
P.S:
I tried to do with a "for" but It seems to omit some promises:
for (var i = 0; i < usersPromises.length; i++) {
usersPromises[i]
.get()
.then(snapshot => {
if(snapshot.exists){
......
var user = snapshot.data()
......
}else{
......
}
})
}
I think its not a problem of getAll. I have tested like this:
const firestore = new Firestore();
let doc = []
doc[0] = firestore.doc('test/test');
doc[1] = firestore.doc('test/test1');
doc[2] = firestore.doc('test/doc');
firestore.getAll(...doc)
.then(result=> result.forEach(doc => console.log(doc._fieldsProto)))
.catch(err=>console.log(err));
In my database I have 'test/test' and 'test/doc' document, but I do not have 'test/test1' and results look like this:
So we just get undefined on document that is not exist and that's all. I suggest to add catch and see if there is any exception. When I have been writing the test the function was interrupted by typo mistake in inner function.
I hope this will help!

Push not working inside firebase listener

var db=firebase.firestore();
var musicidarray=[];
var musicpaircontentarray=[];
//Retreive all music value pairs
db.collection("MusicIdNamePairs").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
musicidarray.push(doc.id);
musicpaircontentarray.push(doc);
//alert(doc.get("Name"));
//console.log(`${doc.id} => ${doc.data()}`);
});
});
alert(musicidarray.length);//Suprisingly outputs length as zero even when the previou loop has run
for(var i=0;i<musicpaircontentarray.length;i++)
{
alert(musicpaircontentarray[i].get("Name"));
}
Here the musicidarray and the musicpaircontentarray (storing the document reference obtained from Cloud Firestore) is showing length as zero even after it has executed the push operation inside the foreach loop in the previous block of code.What is wrong here.Please help me.Thanks a lot for the help.
<script>
var db=firebase.firestore();
var musicidarray=[];
var musicpaircontentarray=[];
//Retreive all music value pairs
db.collection("MusicIdNamePairs").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
musicidarray.push(doc.id);
musicpaircontentarray.push(doc);
//alert(doc.get("Name"));
//console.log(`${doc.id} => ${doc.data()}`);
});
displayarray();
});
function displayarray()
{
alert(musicidarray.length);
for(var i=0;i<musicpaircontentarray.length;i++)
{
alert(musicpaircontentarray[i].get("Name"));
}
}
</script>
The issue here as far as my understanding is that the array.length is called even before the data is retrieved from the database(even though the content is after the loop in the script and looks as if the length is called after only the loop is executed).Instead, call the display array method after the entire loop is assured to be completed as in the solution.
Hope this is the right way. If I am wrong somewhere, please correct me.

Saved (globally defined) array is emptied after callback

I am using the spotify-web-api-js node module, JavaScript, and jQuery to log into a user's Spotify account and save an array of tracks to a newly created playlist, but so far I can only get a blank playlist created.
I gather all of the tracks through the API and put them in a globally defined array as I show the list to the user. This array is defined on line 39: var song_uris = [];
console.log shows that this array is filled with the URIs I need after this function, which occurs upon initial search:
function searchArtists(originalArtist, callback) {
$(window).on('hashchange', function() {
console.log(window.location.hash);
});
console.log('originalArtist', originalArtist);
$.getJSON("https://api.spotify.com/v1/search?type=artist&q=" + originalArtist, function(json) {
$('#artist').html('<p>'+ '<img src="' + json.artists.items[0].images[2].url + '" height="100" width="100" /> ' + json.artists.items[0].name +'</p>');
var originalArtistId = json.artists.items[0].id;
s.getArtistRelatedArtists(originalArtistId, function(err, data) {
relatedArtists = {};
for (var i = 0; i < data.artists.length; i++) {
relatedArtists[data.artists[i].id] = {};
relatedArtists[data.artists[i].id].name = data.artists[i].name;
relatedArtists[data.artists[i].id].id = data.artists[i].id;
}
var counter = 0;
for (var id in relatedArtists) {
relatedArtists[counter] = relatedArtists[id];
delete relatedArtists[id];
counter++;
}
async.times(counter, function(n, next) {
console.log(n);
console.log(relatedArtists[n].id);
s.getArtistTopTracks(relatedArtists[n].id, "US", function (err, data2) {
relatedArtists[n].song = data2.tracks[0].name; //sometimes this is a TypeError? idk
relatedArtists[n].uri = data2.tracks[0].uri;
$('#related-artist').append('<p><strong>' + relatedArtists[n].name + '</strong> -- \"' + relatedArtists[n].song + '\"</p>');
song_uris.push(relatedArtists[n].uri);
next(null, relatedArtists[n].uri);
});
}, function(err, song_uris) {
console.log(song_uris); //array is full here
});
});
});
}
However, as soon as people go through the optional login/callback process, I get all of the user's data (including the new playlist URI), but the song_uris array I had before is now empty.
if (params.access_token) {
s.setAccessToken(params.access_token);
s.getMe().then(function(data) {
// and here it goes the user's data!!!
console.log(data);
console.log(data.id);
user_id = data.id;
s.createPlaylist(user_id, {name: 'Related Artist Playlist'}).then(function(data3) {
console.log(data3);
playlist_id = data3.uri;
playlist_id = playlist_id.substring(33);
console.log(playlist_id);
console.log(user_id);
console.log(song_uris); //array is empty here
s.addTracksToPlaylist(user_id, playlist_id, song_uris).then(function(data){
console.log(data);
});
});
});
}
Since this is one of the parameters of the addTracksToPlaylist function, the XML request doesn't work.
Here is a screenshot of my console, where I see a failed POST request and an uncaught exception: object XMLHttpRequest:
There is obviously an issue with the scope and/or async, but I can't seem to figure out where to put addTracksToPlaylist instead. Putting it immediately after the line where console.log outputs a full array either doesn't do anything (if it's within a window.onhashchange = function ()), or gives an error because the user and playlist are undefined before the callback (if the addTracksToPlaylist function is there alone).
Is the data being overwritten when the site refreshes post-callback? If so, how can I stop that so I have a full array of URIs that fit in the URL string? (According to the Spotify Developer docs, the URL should have the comma-separated list of track URIs (which was in my array) passed to it through the uri parameter, but because of the empty array it breaks and I get https://api.spotify.com/v1/users/account/playlists/5kxeeKym1tpEx8Trj3qkd5/tracks?uris= (with the empty parameter). How can I solve this? Or is there an alternate way to keep the desired user flow of Search for Artist -> See List of Songs from Related Artists -> (Optional) Login To Spotify Account -> (Optional) Save List of Songs from Related Artists to New Playlist?
I've created a jsfiddle with the full JS and HTML code as well: https://jsfiddle.net/37Lkrcb1/1/
Notes: This is running on a Node.js server and I use bundle and browserify to compile the script.
It sounds like at least one path in your app will involve a page refresh you can't avoid (going to Spotify and coming back). In that case, all your variables will be reset by the page load.
If you have song_uris you want to persist across that reload, you can store them in sessionStorage (or localStorage) prior to the Spotify authentication, and retrieve them when the page loads.
Storing it:
sessionStorage.setItem("song_uris", JSON.stringify(song_uris));
// Or localStorage.setItem...
Retrieving it on page load:
var song_uris = JSON.parse(sessionStorage.getItem("song_uris") || "null");
if (!song_uris) {
// There weren't any in storage, populate in another way or set dfeault
}
Web storage is nearly universally supported.

Looping Through Multiple JSON Requests (YouTube Data API)

Part of a website I am working on is a video page. I am pulling the videos from a YouTube account by accessing the YouTube Data API. Grabbing the videos in no particular order and not sorted works fine, but when I try to sort them into categories, I start running into trouble. Let's say there are three categories, Fruit, Vegetable, Pets. Instead of grabbing all the videos at once, I want to grab all the videos tagged with Fruit, append them to a <ul id="Fruit">. Then request all videos tagged with Vegetable, etc.
When starting out, I had the browser alert when it had finished getting the request and then appending the appropriate list. After I took out the alert, it still worked, but not the way I expected. Either the loop is advancing too quickly, or not advancing at all, but I can't seem to spot the mistake. What ends up happening is that all the videos get put into one list, <ul id="Vegetable">.
Please note: I am using a plugin called jGFeed which wraps the jQuery getJSON function, so I believe you can treat it as such.
var videoCategories = ['Fruit', 'Vegetable', 'Pets'];
for (var i = 0; i < videoCategories.length; i++) {
var thisCategory = videoCategories[i];
$.jGFeed('http://gdata.youtube.com/feeds/api/users/username/uploads?category='+thisCategory,
//Do something with the returned data
function(feeds) {
// Check for errors
if(!feeds) {
return false;
} else {
for(var j=0; j < feeds.entries.length(); j++) {
var entry = feeds.entries[i];
var videoUrl = entry.link;
$('ul#'+thisCategory).append('<li>'+entry.title+'</li>');
}
});
}
The problem is, you're using the 'thisCategory'-variable to set the category-name. The problem is, the value if this variable changes, while you're waiting for a response from the server.
You could try to put the whole script inside a function:
var videoCategories = ['Fruit', 'Vegetable', 'Pets'];
for (var i = 0; i < videoCategories.length; i++) {
getCategory(videoCategories[i]);
}
function getCategory(thisCategory)
{
$.jGFeed('http://gdata.youtube.com/feeds/api/users/username/uploads?category='+thisCategory,
//Do something with the returned data
function(feeds) {
// Check for errors
if(!feeds) {
return false;
} else {
for(var j=0; j < feeds.entries.length(); j++) {
var entry = feeds.entries[i];
var videoUrl = entry.link;
$('ul#'+thisCategory).append('<li>'+entry.title+'</li>');
}
});
}
I haven't tested this, so I'm not sure if it works..

Categories

Resources