I have the following problem: I want to get data from a specific node from firebase during runtime. It should display "stats" of a player that was selected before. Now I could use on() to get all the data in the beginning, but I want to save data transfers by only downloading the data of on player if I need to, so I tried to use once like this:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
firebaseRef.once('value').then(function(snap) {
snap.child('scores').child('thierschi').forEach(function(child) {
selectedPlayerScores.push([child.key, child.val()]);
});
});
return selectedPlayerScores;
}
The problem is that it retruns the array before the data was loaded into it. Also I checked the docs and didn't find a better solution.
Thanks in advance!
This is because the getScoresOfPlayer function returns selectedPlayerScores before the promise returned by the once() method resolves.
You should include the return within the then(), as follows:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
return firebaseRef.once('value') //return here as well
.then(function(snap) {
snap.child('scores').child(player).forEach(function(child) { //I guess it should be child(player) and not child('thierschi') here
selectedPlayerScores.push([child.key, child.val()]);
});
return selectedPlayerScores;
});
}
which means that you have to call your function as follows, since it is going to be asynchronous and to return a promise:
getScoresOfPlayer('xyz')
.then(function(selectedPlayerScores) {
....
})
Related
exports.editData = functions.database.ref('/AllData/hello/A').onWrite((change, context) => {
const after = change.after;
if (after.exists()) {
const data = after.val();
var value = data;
// set of data to multiply by turns ratio
var actualEIn = (value.ein)*200;
console.log('Data Edited');
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Edit: made some edits to the code as suggested! However, when I deploy it there are literally no logs.
Change this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((snapshot) => {
const data = snapshot.val();
if (data.exists()) {
into this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change,context) => {
const data = change.after.val();
if (data.exists()) {
more info here:
https://firebase.google.com/docs/functions/beta-v1-diff#realtime-database
exports.editData = functions.database.ref('/AllData/hello/A/{id}').onWrite((change, context) => {
const afterData = change.after;
if (afterData.exists()) {
console.log('hey');
const data = afterData.val();
// set of data to multiply by turns ratio
var actualEIn = (data.ein)*200;
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Hi guys thank you for all your help! :) I managed to solve this by adding a /{id} at the back!
You've got two things wrong here.
First, newer versions of the firebase-functions SDK since version 1.0 deliver a Change object to onWrite handlers instead of a snapshot, as it appears you are expecting. The Change object has properties for before and after with DataSnapshot objects of the contents of the database before and after the change that triggered the function. Please read the documentation for database triggers to get all the information.
Second, exists() is a method on DataSnapshot, but you're using it on the raw JavaScript object value of the contents of the database the location of change. JavaScript objects coming from val() will not have any methods to call.
You should probably update your code to:
Use the latest version of the firebase-functions module
Alter your function to accept the Change object instead of a snapshot
Use the exists() method on a snapshot in the change, rather than a raw JavaScript object.
Starter code:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change) => {
const after = change.after; // the DataSnapshot of the data after it was changed
if (after.exists()) {
const data = after.val() // the raw JavaScript value of the location
// use data here
}
})
Specifically, given a list of data, I want to loop over that list and do a fetch for each element of that data before I combine it all afterward. The thing is, as written, the code iterates through the entire list immediately, starting all the operations at once. Then, even though the fetch operations are still running, the then call I have after all that runs, before the data could've been processed.
I read something about putting all the Promises in an array, then passing that array to a Promise.all() call, followed by a then that will have access to all that processed data as intended, but I'm not sure how exactly to go about doing it in this case, since I have nested Promises in this for loop.
for(var i in repoData) {
var repoName = repoData[i].name;
var repoUrl = repoData[i].url;
(function(name, url) {
Promise.all([fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoData[i].name}/pulls`)])
.then(function(results) {
Promise.all([results[0].json(), results[1].json()])
.then(function(json) {
//console.log(json[0]);
var commits = json[0];
var pulls = json[1];
var repo = {};
repo.name = name;
repo.url = url;
repo.commitCount = commits.length;
repo.pullRequestCount = pulls.length;
console.log(repo);
user.repositories.push(repo);
});
});
})(repoName, repoUrl);
}
}).then(function() {
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});
Generally when you need to run asynchronous operations for all of the items in an array, the answer is to use Promise.all(arr.map(...)) and this case appears to be no exception.
Also remember that you need to return values in your then callbacks in order to pass values on to the next then (or to the Promise.all aggregating everything).
When faced with a complex situation, it helps to break it down into smaller pieces. In this case, you can isolate the code to query data for a single repo into its own function. Once you've done that, the code to query data for all of them boils down to:
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
}))
Please try the following:
// function to query details for a single repo
function getDataForRepo(username, repoInfo) {
return Promise
.all([
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/commits`),
fetch(`https://api.github.com/repos/${username}/${repoInfo.name}/pulls`)
])
.then(function (results) {
return Promise.all([results[0].json(), results[1].json()])
})
.then(function (json) {
var commits = json[0];
var pulls = json[1];
var repo = {
name: repoInfo.name,
url: repoInfo.url,
commitCount: commits.length,
pullRequestCount: pulls.length
};
console.log(repo);
return repo;
});
}
Promise.all(repoData.map(function (repoItem) {
return getDataForRepo(username, repoItem);
})).then(function (retrievedRepoData) {
console.log(retrievedRepoData);
var payload = new Object();
payload.user = user;
//console.log(payload);
//console.log(repoData[0]);
res.send(payload);
});
I'm using this Gumroad-API npm package in order to fetch data from an external service (Gumroad). Unfortunately, it seems to use a .then() construct which can get a little unwieldy as you will find out below:
This is my meteor method:
Meteor.methods({
fetchGumroadData: () => {
const Gumroad = Meteor.npmRequire('gumroad-api');
let gumroad = new Gumroad({ token: Meteor.settings.gumroadAccessKey });
let before = "2099-12-04";
let after = "2014-12-04";
let page = 1;
let sales = [];
// Recursively defined to continue fetching the next page if it exists
let doThisAfterResponse = (response) => {
sales.push(response.sales);
if (response.next_page_url) {
page = page + 1;
gumroad.listSales(after, before, page).then(doThisAfterResponse);
} else {
let finalArray = R.unnest(sales);
console.log('result array length: ' + finalArray.length);
Meteor.call('insertSales', finalArray);
console.log('FINISHED');
}
}
gumroad.listSales(after, before, page).then(doThisAfterResponse); // run
}
});
Since the NPM package exposes the Gumorad API using something like this:
gumroad.listSales(after, before, page).then(callback)
I decided to do it recursively in order to grab all pages of data.
Let me try to re-cap what is happening here:
The journey starts on the last line of the code shown above.
The initial page is fetched, and doThisAfterResponse() is run for the first time.
We first dump the returned data into our sales array, and then we check if the response has given us a link to the next page (as an indication as to whether or not we're on the final page).
If so, we increment our page count and we make the API call again with the same function to handle the response again.
If not, this means we're at our final page. Now it's time to format the data using R.unnest and finally insert the finalArray of data into our database.
But a funny thing happens here. The entire execution halts at the Meteor.call() and I don't even get an error output to the server logs.
I even tried switching out the Meteor.call() for a simple: Sales.insert({text: 'testing'}) but the exact same behaviour is observed.
What I really need to do is to fetch the information and then store it into the database on the server. How can I make that happen?
EDIT: Please also see this other (much more simplified) SO question I made:
Calling a Meteor Method inside a Promise Callback [Halting w/o Error]
I ended up ditching the NPM package and writing my own API call. I could never figure out how to make my call inside the .then(). Here's the code:
fetchGumroadData: () => {
let sales = [];
const fetchData = (page = 1) => {
let options = {
data: {
access_token: Meteor.settings.gumroadAccessKey,
before: '2099-12-04',
after: '2014-12-04',
page: page,
}
};
HTTP.call('GET', 'https://api.gumroad.com/v2/sales', options, (err,res) => {
if (err) { // API call failed
console.log(err);
throw err;
} else { // API call successful
sales.push(...res.data.sales);
res.data.next_page_url ? fetchData(page + 1) : Meteor.call('addSalesFromAPI', sales);
}
});
};
fetchData(); // run the function to fetch data recursively
}
I have a SharePoint App that is built with AngularJS.
I have a controller that is calling a function in a service and it is not returning a value. I am pretty new at angularJS so I am a bit lost.
My Service:
App.factory('uploadAppService', function () {
return {
currentUserPic: function (myProfileProp) {
GetUserProfileInfo(myProfileProp).done(function (data) {
//The large thumbnail pic.
var picUrl = data.d.PictureUrl;
var largePicUrl = picUrl.replace('MThumb', 'LThumb');
console.log(largePicUrl) //the log here is correct. I want to return the largePicUrl back to my controller.
return largePicUrl;
});
}
My Controller call, I want to populate .imageUrl with the url from the service:
$scope.imageUrl = uploadAppService.currentUserPic("PictureUrl");
Thank you in advance.
To me, your currentUserPicfunction doesn't seem to return a value. GetUserProfileInfo indeed returns your largePicUrl, but this value is not used anywhere (if I correctly understand your code).
Shouldn't you use return GetUserProfileInfo(myProfileProp).done(...); ?
Edit: But as RaviH pointed, if the call is asynchronous, you'll still have to handle it in your controller.
I don't see implementation of your GetUserProfileInfo service, but i suppose it's a Deffered object.
So after code
$scope.imageUrl = uploadAppService.currentUserPic("PictureUrl");
finished working - you don't have anything in you variable $scope.imageUrl because your factory function does not return anything.
So, at first you need to modify your factory:
App.factory('uploadAppService', function () {
return {
currentUserPic: function (myProfileProp) {
return GetUserProfileInfo(myProfileProp).done(function (data) {
// ^
// Here - #ababashka's edit
//The large thumbnail pic.
var picUrl = data.d.PictureUrl;
var largePicUrl = picUrl.replace('MThumb', 'LThumb');
console.log(largePicUrl) //the log here is correct. I want to return the largePicUrl back to my controller.
return largePicUrl;
});
}
Return Deffered Object, so after it finished working, you could save your image URL by getting it in the response.
After you need to write next code:
uploadAppService.currentUserPic("PictureUrl").done(
function (response) {
$scope.imageUrl = response;
}
);
to store your URL in $scope's variable.
I am just getting started with coding for FirefoxOS and am trying to get a list of files in a directory.
The idea is to find the name of each file and add it to the array (which works), but I want to return the populated array and this is where I come unstuck. It seems that the array gets populated during the function (as I can get it to spit out file names from it) but when I want to return it to another function it appears to be empty?
Here is the function in question:
function getImageFromDevice (){
var imageHolder = new Array();
var pics = navigator.getDeviceStorage('pictures');
// Let's browse all the images available
var cursor = pics.enumerate();
var imageList = new Array();
var count = 0;
cursor.onsuccess = function () {
var file = this.result;
console.log("File found: " + file.name);
count = count +1;
// Once we found a file we check if there are other results
if (!this.done) {
imageHolder[count] = file.name;
// Then we move to the next result, which call the cursor
// success with the next file as result.
this.continue();
}
console.log("file in array: "+ imageHolder[count]);
// this shows the filename
}
cursor.onerror = function () {
console.warn("No file found: " + this.error);
}
return imageHolder;
}
Thanks for your help!
Enumerating over pictures is an asynchronous call. Essentially what is happening in your code is this:
You are initiating an empty array
You are are telling firefox os to look for pictures on the device
Then in cursor.onsuccess you are telling firefox os to append to the array you have created WHEN it gets back the file. The important thing here is that this does not happen right away, it happens at some point in the future.
Then you are returning the empty array you have created. It's empty because the onsuccess function hasn't actually happened.
After some point in time the onsuccess function will be called. One way to wait until the array is full populated would be to add in a check after:
if (!this.done) {
imageHolder[count] = file.name;
this.continue();
}
else {
//do something with the fully populated array
}
But then of course your code has to go inside the getImageFromDevice function. You can also pass a callback function into the getImageFromDevice function.
See Getting a better understanding of callback functions in JavaScript
The problem is with the aSynchronous nature of the calls you are using.
You are returning (and probably using) the value of imageHolder when it's still empty - as calls to the "onsuccess" function are deferred calls, they happen later in time, whereas your function returns immediately, with the (yet empty) imageHolder value.
You should be doing in this case something along those lines:
function getImageFromDevice (callback){
...
cursor.onsuccess = function () {
...
if (!this.done) {
// next picture
imageHolder[count] = file.name;
this.continue();
} else {
// no more pictures, return with the results
console.log("operation finished:");
callback(imageHolder);
}
}
}
Or use Promises in your code to accomplish the same.
Use the above by e.g.:
getImageFromDevice(function(result) {
console.log(result.length+" pictures found!");
});