So I have been working with protractor about passing/failing tests and I have created a amout of clicks to be done in the script. Basically that to run x amout of clicks and once it is finished then it should pass.
NEW EDIT
it('Click remove button', function (done) {
let allProds = element.all(by.css('div.stock-controller'));
allProds.count()
.then(function (cnt) { // amount of products
let allPromises = []
for(let index=0;index<cnt;index++) {
let section = allProds.get(index),
// message string which include qty in stock
stock_qty_str = section.element(by.css('div.message')).getText(),
// user inputed qty
user_qty_str = section.element(by.css('div.quantity-input input'))
.getAttribute('value'),
// button Descrease
btn_dec = section.element(by.css('button[aria-label="Decrease"]'));
allPromises.push(Promise.all([stock_qty_str, user_qty_str])
.then(function(data){
// use RegExp to extract qty in stock
let group = data[0].trim().match(/^Sorry.*?(\d+)/)
if(group) {
let stock_qty = group[1] * 1,
user_qty = data[1].trim() * 1,
gap = user_qty - stock_qty; // click times of Decrease button
for(let i=0;i<gap;i++) {
btn_dec.click();
browser.sleep(1000).then(function(){
console.log('Click Decrease button: ' + i + '/' + gap)
})
}
}
})
)
}
return Promise.all(allPromises)
})
.then(()=>{
done();
})
});
However my problem is that what it does now is that:
as you can see what happens is that it counts how many times it is supposed to click and then marks it as finished but yet it still clicks after the passed which is odd I would say...
I wonder how can I make it wait until the function is fully done and then mark it as passed/failed?
You need to return a promise so the test waits for that promise.
At the moment, the promise returns right away without waiting for the clicks.
You need to gather all Promise.all from the for and return that as a promise (using Promise.all again probably)
Something like this
it('Click remove button', function (done) {
let allProds = element.all(by.css('div.stock-controller'));
allProds.count()
.then(function (cnt) { // amount of products
let allPromises = []
for(let index=0;index<cnt;index++) {
let section = allProds.get(index),
// message string which include qty in stock
stock_qty_str = section.element(by.css('div.message')).getText(),
// user inputed qty
user_qty_str = section.element(by.css('div.quantity-input input'))
.getAttribute('value'),
// button Descrease
btn_dec = section.element(by.css('button[aria-label="Decrease"]'));
allPromises.push(Promise.all([stock_qty_str, user_qty_str])...)
}
return Promise.all(allPromises)
})
.then(()=>{
done();
})
});
Related
I have an async function that on page load it runs a function that gets a JSON file and stores it in songResults. it then gets a random object from the JSON and takes some parameters from it and uses them to set the sources for some elements in my HTML file. I am having an issue with the callback for the event listener, the parameter I am passing through guessingGame() is undefined inside the guessingGame() function and I'm not sure why. any help would be muchly appreciated.
JS code
//A async function to run when the page loads to get the first song and album cover to display.
const onPageLoad = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
//An event listener for the submit button to run the guessing game function.
submitButton.addEventListener("click", guessingGame(randomSong.song_path))
};
//async function for when the button is clicked to check the answer in the input box to the json data.
const guessingGame = async (songPath) => {
//get the value of the input box
let input = document.getElementById("guessInputBox").value;
//check if the value of the input box matches the song path in the json data
if (input) {
if (input === songPath) {
alert('correct')
score++;
alert("that took " + score + " attempts");
score = 0;
changeSong();
} else {
alert('incorrect')
alert(songPath);
score++;
};
};
};
What a json file response looks like
{
"song_name": "Listen to Us",
"release_date": "1/05/2012",
"album": "Home Brew",
"photo_path": "/pictures/home-brew.jpg",
"song_path": "/songs/homebrewski-listenToUs.mp3"
}
so the new code updates it kind of it works the first time you guess but the second time you guess it says your incorrect then correct and keeps looping heaps really weird and lags out the browser.
const onPageLoad = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
//An event listener for the submit button to run the guessing game function.
submitButton.addEventListener("click", () => {
guessingGame(randomSong.song_name);
});
};
//async function for when the button is clicked to check the answer in the input box to the json data.
const guessingGame = async (songPath) => {
//get the value of the input box
let input = document.getElementById("guessInputBox").value;
//check if the value of the input box matches the song path in the json data
if (input) {
if (input.toLowerCase() === songPath.toLowerCase()) {
alert('correct')
score++;
alert("that took " + score + " attempts");
score = 0;
changeSong();
} else {
alert('incorrect')
alert(songPath);
score++;
};
};
};
//need to change this to an async function once figured out how to store data.
const changeSong = async () => {
let songResults = await getSongData();
let randomSong = songResults[Math.floor(Math.random() * songResults.length)];
audioSound.src = randomSong.song_path;
audioSound.load();
albumCover.src = randomSong.photo_path;
submitButton.addEventListener("click", () => {
guessingGame(randomSong.song_name);
});
};
Updated Answer
Thank you for posting more code. It looks like you may have mistakenly passed along the song_name instead of the song_path:
// From the onPageLoad and changeSong functions
guessingGame(randomSong.song_name);
// From the guessingGame function
input.toLowerCase() === songPath.toLowerCase()
Another thing to consider is your click-handler; you're adding one to the submitButton every time a song is loaded. As such, the first time you click the button, a single event handler is called. The second time you click, two are called. The third time, three, etc.
Your submit button behavior really only needs to be set once, and forgotten. It's job is to do one thing: see if the user's selection matches the currently-playing song. And, if the user got the correct answer, load another song:
// Minimal game state
let score = 0;
let songs = [];
let songIndex = -1;
// References to game elements
const submit = document.querySelector("#submit")
const selection = document.querySelector("options");
// Begin game functionality
async function onPageLoad () {
// Load meta data for all songs into `songs`
songs = await getSongData();
// TODO: Populate #options based on `songs` data
setRandomSong();
}
function setRandomSong () {
songIndex = Math.floor( Math.random() * songs.length );
audioSound.src = songs[ songIndex ].path;
albumCover.src = songs[ songIndex ].photo;
}
// This will be called whenever a song is changed
audioSound.addEventListener( "load", function audioLoaded () {
console.log( "Audio has loaded, game can be played." );
});
// We need only a single handler for our guess-button
submit.addEventListener( "click", function guess () {
// Get our values to compare
const guess = selection.value;
const answer = songs[ songIndex ].name;
// Increment the number of attempts
score += 1;
// Check lowercase values for equality
// If the answer is false, share number of attempts
if ( guess.toLowerCase() !== answer.toLowerCase() ) {
alert( `You have guessed ${ score } times.` );
return;
}
// If the answer was correct, reset game state
alert( `Correct! And after ${ score } guesses.` );
score = 0;
setRandomSong();
});
Note, this code is untested, but should clearly define a suitable approach to your problem. Please feel free to engage further within the comments as needed.
Original Answer
The addEventListener method expects a function as the second argument. Look closely at your code:
submitButton.addEventListener("click", guessingGame(randomSong.song_path))
Note that you're executing guessingGame, rather than referencing it. Instead, you'd want to provide another function:
submitButton.addEventListener("click", function () {
guessingGame( randomSong.song_path );
});
Now, when the submitButton is clicked, our anonymous function will be called, which in turn will pass randomSong.song_path to guessingGame.
I have function that uploads files from an array of data links
What I would like to do is if data links array contains 3 files
const testLinks = 3; and async uploadImageData is fired three times I would like to console.log after uploadImageData is fired three times.
I am thinking of doing a count but all my testing has the count starting over everytime uploadImageData is fired.
.ts
async uploadImageData(formData: FormData) {
const testLinks = 3;
const uploadlink = answerAtachmentUrl;
const headers = headerLink;
const loading = await this.loadingController.create({
message: 'Uploading Photos and Files...',
});
await loading.present();
this.httpClient.post<any>( uploadlink + this.userToken, formData,
{ 'headers':headers }
).pipe(
finalize(() => { loading.dismiss();})
)
.subscribe(res => {
if (res['success']) {
setTimeout(() => { this.DeleteAllFiles(); }, 5000);
this.presentToastPhoto('Photo sync success.');
} else {
this.presentToastPhoto('Photo upload failed.');
let respFail = JSON.stringify(res);
console.log("respFail", respFail);
}
});
// console.log fires once after count and const testLinks both equal 3
}
"I am thinking of doing a count but all my testing has the count starting over evertime uploadImageData is fired."
you didn't post what you tried but you probably did not make the counter a global variable. Your code is clearly part of a larger project so I just made this small test to show that it works with async functions. If it doesn't work, let me know.
var count=0;
async function uploadImageData() {
const testLinks = 3;
count++;
console.log(count);
if (count === 3){
console.log('count = 3');
}
}
uploadImageData();
uploadImageData();
uploadImageData();
uploadImageData();
My code is intended to loop through an array of sports team IDs to use them to compile various information from an API then add HTML to my page to display the most recently finished game for each team. It is working and looking great EXCEPT: the results are appearing in on my page in a random order each time I refresh. I'm thinking this has something to do with the loop moving to the next iteration quickly, while the responses from the server are coming back at random times. I expect the result to be "Patriots" first, then "Steelers" etc but the result is random, with Patriots hardly ever coming up first.
Also, I'm very new to JS, so I'm sure there is a ton I could do to make this better, so thank you in advance!
//Define the teams I want scores from and their API reference numbers.
let teams = new Map();
teams.set("Patriots", 134920);
teams.set("Steelers", 134925);
teams.set("Bruins", 134830);
teams.set("Penguins", 134844);
teams.set("Celtics", 134860);
teams.set("Red Sox", 135252);
teams.set("Huskers", 136923);
let teamArr = Array.from(teams.values());
for (i = 0; i < teamArr.length; i++) {
console.log(teamArr[i]);
}
//Get the team data so that we can pull the logo image.
async function getTeamData(teamID) {
let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/lookupteam.php?id=${teamID}`)
let teamData = await result.json();
return teamData.teams[0];
}
//Get the info for the teams last game.
async function getLastGame(teamID) {
let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/eventslast.php?id=${teamID}`)
const lastGames = await result.json();
const lastGame = lastGames.results[0];
return lastGame;
};
//Populate the final scores with new HTML after pulling all the info from the API.
for (let i = 0; i < teamArr.length; i++) {
let homeTeam, awayTeam, homeTeamData, awayTeamData, homeTeamLogo, gameDay;
getLastGame(teamArr[i])
.then(lastGame => {
gameDay = lastGame.dateEvent;
homeTeam = {
name: lastGame.strHomeTeam,
id: lastGame.idHomeTeam,
score: lastGame.intHomeScore,
};
awayTeam = {
name: lastGame.strAwayTeam,
id: lastGame.idAwayTeam,
score: lastGame.intAwayScore
}; //This is all the info we need except for the team icons.
}).then(result => {
homeTeamData = getTeamData(homeTeam.id)
return homeTeamData;
}).then(result => {
homeTeam.logo = result.strTeamBadge;
}).then(() => {
awayTeamData = getTeamData(awayTeam.id)
return awayTeamData;
}).then(result => {
awayTeam.logo = result.strTeamBadge; //After getting who was home and who was away, these let us pull and put the right icon in the table.
}).then(() => {
let html = ` <tr>
<th><img src="%awayImg%" alt="Away" id="${i}-away" class="team-logo"></th>
<th><div class="at-vs">#</div></th>
<th><img src="%homeImg" alt="Home" id="${i}-home" class="team-logo"></th>
</tr>
<tr>
<th><div class="away-score">%awayScore%</div></th>
<th><div class="gameday">%gameDay%</div></th>
<th><div class="home-score">%homeScore%</div></th>
</tr>`;
let newhtml = html.replace(`%awayImg%`, awayTeam.logo + "/preview");
newhtml = newhtml.replace(`%homeImg`, homeTeam.logo + "/preview");
newhtml = newhtml.replace(`%awayScore%`, awayTeam.score);
newhtml = newhtml.replace(`%gameDay%`, gameDay);
newhtml = newhtml.replace(`%homeScore%`, homeTeam.score);
document.querySelector(`.past-games-table`).insertAdjacentHTML(`beforeend`, newhtml);
})
};
It seems that what you want is to make concurrent requests to get the data but still show them in a certain order no matter when they arrive.
Right now you are making all your requests one right after the other and showing the results as you get them. This means that the order in which the results are shown is unpredictable, any of the previous requests can finish before any of the subsequent ones and your data will be shown out of order.
Try running this snippet multiple times, the display order is unpredictable:
Note, in the examples below I'm using some fake functions to represent the requesting of data and displaying it. fakeRequest is a function that can represent all but the last function in your promise chain (all the logic you use to get and build your team data) and fakeDisplay is function that can represent the last part of your chain that appends HTML and displays the data.
const fakeRequest = n => new Promise(
resolve => setTimeout(() => {
console.log(`requesting ${n}`);
resolve(n);
}, Math.random() * 1000)
);
const fakeDisplay = n => console.log(`displaying ${n}`);
// all requests are made one after the other
// and as soon as a request is done, we display
// the data, no matter which request that is
for (let i = 0; i < 3; i++) {
fakeRequest(i).then(fakeDisplay);
}
The main issue here is that your displaying logic is tied together with the logic that gets the team data. Splitting this behavior would allow you to tackle your problem in multiple ways.
One way to fix this is to not make a request (and not display the data) until the previous request+display operation is finished:
const fn = async () => {
// note: using await is only valid in an async function
const fakeRequest = n => new Promise(
resolve => setTimeout(() => {
console.log(`requesting ${n}`);
resolve(n);
}, Math.random() * 1000)
);
const fakeDisplay = n => console.log(`displaying ${n}`);
// make each request and wait for it to finish
// then display the result and start next request
for (let i = 0; i < 3; i++) {
const item = await fakeRequest(i);
fakeDisplay(item);
}
}
fn();
Another way to alleviate this is to wait until all your requests are finished before displaying all the results at once (by using Promise.all):
const fakeRequest = n => new Promise(
resolve => setTimeout(() => {
console.log(`requesting ${n}`);
resolve(n);
}, Math.random() * 1000)
);
const fakeDisplay = n => console.log(`displaying ${n}`);
// make the requests one after the other
// and save all the promises in an array
const promises = []
for (let i = 0; i < 3; i++) {
promises.push(fakeRequest(i));
}
// then use a built-in utility Promise.all to
// display all the results once they all arrive
Promise.all(promises)
.then(results => {
for (const item of results) {
fakeDisplay(item);
}
})
The main issue with this is that your UI will not show anything until all requests are done which can take some time and might not be the ideal experience.
Perhaps your UX would be best if you make all requests around the same time but still show the results as they arrive, in the right the order.
const fn = async () => {
const fakeRequest = n => new Promise(
resolve => setTimeout(() => {
console.log(`requesting ${n}`);
resolve(n);
}, Math.random() * 1000)
);
const fakeDisplay = n => console.log(`displaying ${n}`);
// make the requests one after the other
// and save all the promises in an array
const promises = []
for (let i = 0; i < 3; i++) {
promises.push(fakeRequest(i));
}
// loop through the promises and resolve each
// one and display the data
for await (const item of promises) {
fakeDisplay(item);
}
}
fn();
You are mixing up two patterns that achieve the same thing. Here is your code refactored with async await
//Define the teams I want scores from and their API reference numbers.
let teams = new Map();
teams.set("Patriots", 134920);
teams.set("Steelers", 134925);
teams.set("Bruins", 134830);
teams.set("Penguins", 134844);
teams.set("Celtics", 134860);
teams.set("Red Sox", 135252);
teams.set("Huskers", 136923);
let teamArr = Array.from(teams.values());
for (i = 0; i < teamArr.length; i++) {
console.log(teamArr[i]);
}
//Get the team data so that we can pull the logo image.
async function getTeamData(teamID) {
let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/lookupteam.php?id=${teamID}`)
let teamData = await result.json();
return teamData.teams[0];
}
//Get the info for the teams last game.
async function getLastGame(teamID) {
let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/eventslast.php?id=${teamID}`)
const lastGames = await result.json();
const lastGame = lastGames.results[0];
return lastGame;
};
//Populate the final scores with new HTML after pulling all the info from the API.
for (let i = 0; i < teamArr.length; i++) {
let homeTeam, awayTeam, homeTeamData, awayTeamData, homeTeamLogo, gameDay;
const lastGame = await getLastGame(teamArr[i]);
gameDay = lastGame.dateEvent;
homeTeam = {
name: lastGame.strHomeTeam,
id: lastGame.idHomeTeam,
score: lastGame.intHomeScore,
};
awayTeam = {
name: lastGame.strAwayTeam,
id: lastGame.idAwayTeam,
score: lastGame.intAwayScore
}; //This is all the info we need except for the team icons.
homeTeamData = await getTeamData(homeTeam.id);
homeTeam.logo = homeTeamData.strTeamBadge;
awayTeamData = await getTeamData(awayTeam.id);
awayTeam.logo = awayTeamData.strTeamBadge; //After getting who was home and who was away, these let us pull and put the right icon in the table.
let html = ` <tr>
<th><img src="%awayImg%" alt="Away" id="${i}-away" class="team-logo"></th>
<th><div class="at-vs">#</div></th>
<th><img src="%homeImg" alt="Home" id="${i}-home" class="team-logo"></th>
</tr>
<tr>
<th><div class="away-score">%awayScore%</div></th>
<th><div class="gameday">%gameDay%</div></th>
<th><div class="home-score">%homeScore%</div></th>
</tr>`;
let newhtml = html.replace(`%awayImg%`, awayTeam.logo + "/preview");
newhtml = newhtml.replace(`%homeImg`, homeTeam.logo + "/preview");
newhtml = newhtml.replace(`%awayScore%`, awayTeam.score);
newhtml = newhtml.replace(`%gameDay%`, gameDay);
newhtml = newhtml.replace(`%homeScore%`, homeTeam.score);
document.querySelector(`.past-games-table`).insertAdjacentHTML(`beforeend`, newhtml);
};
One thing you can do in async functions is to await the resolution of a Promise. In a for loop, this means you won't continue executing the loop until your previous iteration has resolved. Here's a trivial example. You will likely have a similar result if you aggregate all your fetches in an array (e.g., [getLastGame(teamArr[0]), getLastGame(teamArr[1]), getLastGame(teamArr[2]), etc.]) and then loop over that array using await on each element.
const timed = (id) => new Promise(res => {
setTimeout(() => res(id), Math.random() * 2000);
})
const func = async () => {
const promises = [timed(1), timed(2), timed(3), timed(4)];
for (let promise of promises) {
const returned = await promise;
console.log(returned);
}
}
func();
Something else you can consider is, again aggregating all your promises, but then using Promise.all to await the resolution of all of the promises to then do something when all your data have arrived:
Promise.all([getLastGame(teamArr[0]), getLastGame(teamArr[1], etc.)])
.then(all => {
// the `all` variable will contain an array of all resolved promises
})
I need to change the text and style of the "Get next" button to "Loading...",
Synchronously retrieve a random number of record IDs from a "server" and Asynchronously retrieve the corresponding records from the "server", only proceeding when all records have been received.
Sort the records in date order, oldest first and at the end reset the button to its original state
The code is as follows
let loading = true;
const buttonHandler = function () {
loading = !loading;
toggleButton(loading);
getRecords();
};
const btn = document.getElementById('get-records');
btn.addEventListener('click', buttonHandler);
function toggleButton(loaded) {
btn.innerHTML = loaded ? 'Loading...' : 'Get next';
btn.classList.toggle('button-not-loading');
btn.classList.toggle('button-loading');
}
function getRecords() {
// getting the IDs of the records to fetch is a synchronous operation
// you don't need to change this call, it should return the IDs
const ids = Server.getIds();
const allTheRecords = [];
// getting each corresponding record is an async operation
ids.forEach(function (recordId) {
Server.getRecord(recordId, function (error, data) {
// if the fetch is unsuccessful the callback function is invoked with the error only
// if the fetch is successful the callback is invoked with error variable set to null,
// and data variable will hold the response (i.e. the record you wanted to retrieve)
if (error) {
console.log(error);
} else {
error = null;
allTheRecords.push(data);
}
});
// you can get a SINGLE record by calling Server.getRecord(recordId, callbackFunction)
// callbackFunction takes 2 parameters, error and data
// invocation as follows
// you need to make sure the list is not rendered until we have the records...
//but need to allow for any fetch errors or app will hang
// i.e. a record you request might not exist - how would you allow for this?
// when you have the records, call processRecords as follows
processRecords(allTheRecords);
});
}
function processRecords(records) {
toggleButton(true);
const sortedRecords = sortRecords(records);
let html = '';
let tr;
sortedRecords.forEach(function (index, value, array) {
tr = '';
tr +=
'<tr>' +
'<td>' + value.date + '</td>' +
'<td>' + value.name + '</td>' +
'<td>' + value.natInsNumber + '</td>' +
'<td>' + value.hoursWorked + '</td>' +
'<td>' + value.hourlyRate + '</td>' +
'<td>' + (value.hoursWorked * value.hourlyRate) + '</td>' +
'</tr>';
html += tr;
});
document.getElementById('results-body').innerHTML = html;
addTotals(sortedRecords);
}
function sortRecords(records) {
let sorted = records.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
// sort results in date order, most recent last
return sorted;
}
function addTotals(records) {
let hours = 0;
let paid = 0;
records.forEach(function (value, index) {
hours += value.hoursWorked;
paid += (value.hoursWorked * value.hourlyRate);
});
document.getElementById('totals-annot').innerHTML = 'TOTALS';
document.getElementById('totals-hours').innerHTML = hours;
document.getElementById('totals-paid').innerHTML = paid;
}
there is no question there, but ill give a vague pseudo code answer which should be enough to point you in the right direction.
Keyword = Promise.
const loadRecordIds = () => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/recordIds').then((data) => {
// do something with the data ... e.g parse/validate
resolve(data);
});
});
};
const loadRecords = (recordIds) => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/records?recordIds='+recordIds).then((data) => {
// check the data for errors etc
resolve(data);
});
});
};
const toggleButton = () => {
// toggle your button styles
};
// and you use the functions in sequence using .then() or async keyword(if you have a preprocessor or dont care about old browsers)
loadRecordIds().then((recordIds) => {
// now you have your recordIds loaded
toggleButton();
loadRecords(recordIds).then((records) => {
// now you have your records available for further processing
});
});
// with async await keywords you could do the same like this.
try {
const recordIds = await loadRecordIds();
toggleButton();
const records = await loadRecords(recordIds);
} catch (error) {
// handle errors
}
If you dont know what promises are, google them.
// ok, ill throw in a quick sample of an async code that runs in "sync" using promises.
step1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// time has run out now, and its time for the second step
// calling "resolve" will call the "then" function and allows the code to continue
// whatever you pass in as the argument for resolve() will be a parameter in the "then()" function callback.
resolve('3000 seconds has passed, time to continue.');
}, 3000);
});
};
step2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2000 seconds has passed, time to continue.');
}, 2000);
});
};
step1().then((message) => {
console.log(message);
step2().then((message) => {
console.log(message);
setTimeout(() => {
console.log('and now the script is done...all in sequence');
}, 2000);
});
});
/*
this will output
3000 seconds has passed, time to continue.
2000 seconds has passed, time to continue.
and now the script is done...all in sequence
*/
I think a firebase function updating a list that I have in the firebase database is being captured by a subscription that is subscribed to that list. From what the list output looks like on my phone (in the app)...and from what my console output looks like (the way it repeats) it seems like it is capturing the whole list and displaying it each time one is added. So (I looked this up)...I believe this equation represents what is happening:
(N(N + 1))/2
It is how you get the sum of all of the numbers from 1 to N. Doing the math in my case (N = 30 or so), I get around 465 entries...so you can see it is loading a ton, when I only want it to load the first 10.
To show what is happening with the output here is a pastebin https://pastebin.com/B7yitqvD.
In the output pay attention to the array that is above/before length - 1 load. You can see that it is rapidly returning an array with one more entry every time and adding it to the list. I did an extremely rough count of how many items are in my list too, and I got 440...so that roughly matches the 465 number.
The chain of events starts in a page that isn't the page with the list with this function - which initiates the sorting on the firebase functions side:
let a = this.http.get('https://us-central1-mane-4152c.cloudfunctions.net/sortDistance?text='+resp.coords.latitude+':'+resp.coords.longitude+':'+this.username);
this.subscription6 = a.subscribe(res => {
console.log(res + "response from firesbase functions");
loading.dismiss();
}, err => {
console.log(JSON.stringify(err))
loading.dismiss();
})
Here is the function on the page with the list that I think is capturing the entire sort for some reason. The subscription is being repeated as the firebase function sorts, I believe.
loadDistances() {
//return new Promise((resolve, reject) => {
let cacheKey = "distances"
let arr = [];
let mapped;
console.log("IN LOADDISTANCES #$$$$$$$$$$$$$$$$$$$$$");
console.log("IN geo get position #$$$$$$$5354554354$$$$$$$");
this.distancelist = this.af.list('distances/' + this.username, { query: {
orderByChild: 'distance',
limitToFirst: 10
}});
this.subscription6 = this.distancelist.subscribe(items => {
let x = 0;
console.log(JSON.stringify(items) + " length - 1 load");
items.forEach(item => {
let storageRef = firebase.storage().ref().child('/settings/' + item.username + '/profilepicture.png');
storageRef.getDownloadURL().then(url => {
console.log(url + "in download url !!!!!!!!!!!!!!!!!!!!!!!!");
item.picURL = url;
}).catch((e) => {
console.log("in caught url !!!!!!!$$$$$$$!!");
item.picURL = 'assets/blankprof.png';
});
this.distances.push(item);
if(x == items.length - 1) {
this.startAtKey4 = items[x].distance;
}
x++;
})
//this.subscription6.unsubscribe();
})
}
The subscription in loadDistances function works fine as long as I don't update the list from the other page - another indicator that it might be capturing the whole sort and listing it repeatedly as it sorts.
I have tried as as I could think of to unsubscribe from the list after I update...so then I could just load the list of 10 the next time the page with the list enters, instead of right after the update (over and over again). I know that firebase functions is in beta. Could this be a bug on their side? Here is my firebase functions code:
exports.sortDistance = functions.https.onRequest((req, res) => {
// Grab the text parameter.
var array = req.query.text.split(':');
// Push the new message into the Realtime Database using the Firebase Admin SDK.
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("profiles/stylists");
var promises = [];
// Attach an asynchronous callback to read the data at our posts reference
ref.on("value", function(snapshot) {
//console.log(snapshot.val());
var snap = snapshot.val();
for(const user in snap) {
promises.push(new Promise(function(resolve, reject) {
var snapadd = snap[user].address;
console.log(snapadd + " snap user address (((((((())))))))");
if(snapadd != null || typeof snapadd != undefined) {
googleMapsClient.geocode({
address: snapadd
}).asPromise()
.then(response => {
console.log(response.json.results[0].geometry.location.lat);
console.log(" +++ " + response.json.results[0].geometry.location.lat + ' ' + response.json.results[0].geometry.location.lng + ' ' + array[0] + ' ' + array[1]);
var distanceBetween = distance(response.json.results[0].geometry.location.lat, response.json.results[0].geometry.location.lng, array[0], array[1]);
console.log(distanceBetween + " distance between spots");
var refList = db.ref("distances/"+array[2]);
console.log(snap[user].username + " snap username");
refList.push({
username: snap[user].username,
distance: Math.round(distanceBetween * 100) / 100
})
resolve();
})
.catch(err => { console.log(err); resolve();})
}
else {
resolve();
}
}).catch(err => console.log('error from catch ' + err)));
//console.log(typeof user + 'type of');
}
var p = Promise.all(promises);
console.log(JSON.stringify(p) + " promises logged");
res.status(200).end();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
});
What is weird is, when I check the firebase functions logs, all of this appears to only run once...but I still think the subscription could be capturing the whole sorting process in some weird way while rapidly returning it. To be as clear as possible with what I think is going on - I think each stage of the sort is being captured in an (N(N + 1))/2...starting at 1 and going to roughly 30...and the sum of the sorting ends up being the length of my list (with 1-10 items repeated over and over again).
I updated to angularfire2 5.0 and angular 5.0...which took a little while, but ended up solving the problem:
this.distanceList = this.af.list('/distances/' + this.username,
ref => ref.orderByChild("distance").limitToFirst(50)).valueChanges();
In my HTML I used an async pipe, which solved the sorting problem:
...
<ion-item *ngFor="let z of (distanceList|async)" no-padding>
...