I'm trying to get data from firebase firestore inside my firebase cloud functions, my index.js looks like this:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.writeReport = functions.https.onRequest((request, response) => {
var fetchedData;
db.collection("mycollection").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
fetchedData = doc.id;
console.log("data" + `${doc.id} => ${doc.data()}`);
});
});
response.send("data " + fetchedData);
});
This code gives me an output of "data undefined" so I guess this means that the foreach statement doesn't get executed.
I am new to google cloud so I've been looking through the documentation and I can't fins anything which is related to my issue.
Thanks in advance,
Guy.
db.collection("mycollection").get() is asynchronous and returns immediately. The then callback function will get executed some time later, after the query completes. then() is also asynchronous. In fact anything that returns a promise is async.
This means that your call to response.send() is executing before the query completes. The function terminates as soon as the response is sent, so the query never completes. You need to wait for the query to complete before sending the response. The easiest thing to do is simply call response.send from inside the callback.
db.collection("mycollection").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
fetchedData = doc.id;
console.log("data" + `${doc.id} => ${doc.data()}`);
});
response.send("data " + fetchedData);
});
If you're new to JavaScript, I strong suggest spending time learning how promises work. There is a video series on learning how to use promises in Cloud Functions that you might find helpful.
Related
I am working on a piece of my app where I need to make a call to a Firebase Function, which parses through Firestore data to return a dictionary that I will use to populate UI in Swift.
My function is declared as so:
exports.getUIBetData = functions.https.onRequest( (request, response) => {
This function takes in a userID as body parameter. Then, I need to hit firebase to get a specific document's data tied to this userId and perform some actions on it. I believe I am running into some issues with the async functionality behind getting data from a document, as I keep getting errors or simple promises that haven't been resolved. Here is my query:
const body = request.body;
const userId = body.data.userId;
const bettorIdDoc = admin.firestore()
.collection("user_dim").doc(userId).get().data();
I can confirm that "user_dim" is a valid collection, and the userId is a key to a document within it. However, I can't access the fields tied to this doc.
I was originally trying with just .data(), and realized from the official documentation that you need to do .get().data(), however this is async. How do I handle the async nature when I am attempting to do this within my main function (exports.getUIBetData = functions.https.onRequest( (request, response) => {)?
Error:
TypeError: admin.firestore(...).collection(...).doc(...).get(...).data is not a function
Loading data from Firestore (and pretty much any cloud API) is an asynchronous operation. You can see this by checking the return type of get(), which is Promise<DocumentSnapshot> and not just DocumentSnapshot.
This means you'll have to use then or await (if you're in an async context) to be able call data():
const bettorIdRef = admin.firestore()
.collection("user_dim").doc(userId)
ref.get().then((snapshot) => console.log(snapshot.data());
I am trying to send data from my firebase database back to my client in a cloud function. I want to return the entire json child object. Here is the code for my cloud function:
exports.addNumbers = functions.https.onCall((data, context) => {
admin.database().ref('store/ingredients').once('value', function(snapshot) {
return snapshot.val();
});
});
Here is my client code that is invoking this cloud function and accessing its data:
const addNumbers = firebase.functions().httpsCallable('addNumbers');
addNumbers().then(result => {
console.log(result.data + "in client");
});
But the data returned in the client is null and the cloud function is returning only object [Object] in the firebase logs. Please someone help me.
You're almost there. You just need to return something from the top-level code in your Cloud Function.
The easiest way is to use a then clause:
exports.addNumbers = functions.https.onCall((data, context) => {
return admin.database().ref('store/ingredients').once('value').then((snapshot) => {
return snapshot.val();
});
});
Now the value from the database "bubbles up" to the calling code, and is then returned out of your function as a promise.
On modern JavaScript versions, you can make this code easier to read by using async/await:
exports.addNumbers = functions.https.onCall(async (data, context) => {
let snapshot = await admin.database().ref('store/ingredients').once('value')
return snapshot.val();
});
This works exactly the same under the hood (it's really just syntactic sugar), but most developers find it much easier to read.
I am using node and axios (with TS, but that's not too important) to query an API. I have a suite of scripts that make calls to different endpoints and log the data (sometimes filtering it.) These scripts are used for debugging purposes. I am trying to make these scripts "better" by adding a delay between requests so that I don't "blow up" the API, especially when I have a large array I'm trying to pass. So basically I want it to make a GET request and pause for a certain amount of time before making the next request.
I have played with trying setTimeout() functions, but I'm only putting them in places where they add the delay after the requests have executed; everywhere I have inserted the function has had this result. I understand why I am getting this result, I just had to try everything I could to at least increase my understanding of how things are working.
I have though about trying to set up a queue or trying to use interceptors, but I think I might be "straying far" from a simpler solution with those ideas.
Additionally, I have another "base script" that I wrote on the fly (sorta the birth point for this batch of scripts) that I constructed with a for loop instead of the map() function and promise.all. I have played with trying to set the delay in that script as well, but I didn't get anywhere helpful.
var axios = require('axios');
var fs = require('fs');
const Ids = [arrayOfIds];
try {
// Promise All takes an array of promises
Promise.all(Ids.map(id => {
// Return each request as its individual promise
return axios
.get(URL + 'endPoint/' + id, config)
}))
.then((vals) =>{
// Vals is the array of data from the resolved promise all
fs.appendFileSync(`${__dirname}/*responseOutput.txt`,
vals.map((v) => {
return `${JSON.stringify(v.data)} \n \r`
}).toString())
}).catch((e) => console.log)
} catch (err) {
console.log(err);
}
No errors with the above code; just can't figure out how to put the delay in correctly.
You could try Promise.map from bluebird
It has the option of setting concurrency
var axios = require('axios');
var fs = require('fs');
var Promise = require('bluebird');
const Ids = [arrayOfIds];
let concurrency = 3; // only maximum 3 HTTP request will run concurrently
try {
Promise.map(Ids, id => {
console.log(`starting request`, id);
return axios.get(URL + 'endPoint/' + id, config)
}, { concurrency })
.then(vals => {
console.log({vals});
})
;
} catch (err) {
console.log(err);
}
Background
I am writing some asynchronous code in express. In one of my end points there I need to retrieve some data from firebase for 2 seperate things.
one posts some data
the other retrieves some data to be used in a calculation and another post.
These 2 steps are not dependent on one another but obviously the end result that should be returned is (just a success message to verify that everything was posted correctly).
Example code
await postData(request);
const data = await retrieveUnrelatedData(request);
const result = calculation(data);
await postCalculatedData(result);
In the code above postData will be holding up the other steps in the process even though the other steps (retrieveUnrelatedData & postCalculatedData) do not require the awaited result of postData.
Question
Is there a more efficient way to get the retrieveUnrelatedData to fire before the full postData promise is returned?
Yes, of course! The thing you need to know is that async/await are using Promises as their underlying technology. Bearing that in mind, here's how you do it:
const myWorkload = request => Promise.all([
postData(request),
calculateData(request)
])
const calculateData = async request => {
const data = await retrieveUnrelatedData(request);
const result = calculation(data);
return await postCalculatedData(result);
}
// Not asked for, but if you had a parent handler calling these it would look like:
const mainHandler = async (req, res) => {
const [postStatus, calculatedData] = await myWorkload(req)
// respond back with whatever?
}
I using Cloud Functions for Firebase to build HTTP endpoint. Inside this endpoint I am trying to read some data from Firebase Realtime Database synchronously by this method:
function getSomethingFromDbSynchronously (currency) {
return new Promise((resolve, reject) => {
var db = admin.database();
var ref = db.ref("someref");
ref.orderByChild("somechild").equalTo("something").once("value", function (snapshot) {
resolve(snapshot);
});
});
}
But it is doesn't works for me. My API route returns by the first return statement before this request to the DB ends.
What am I do wrong ?
The code looks fine: you're creating a new promise and returning that from getSomethingFromDbSynchronously(). But the code that calls getSomethingFromDbSynchronously() will then need to wait for the promise to resolve, with something like:
getSomethingFromDbSynchronously("currency").then(function(snapshot) {
console.log(snapshot.val());
});
There is no way to make this synchronous, although you could look into the new async and await keywords, which simply make the above read as if it happens synchronously.
Note, that your code is a bit longer than needed. Since once() already returns a promise, you might as well return that directly:
function getSomethingFromDbSynchronously (currency) {
var db = admin.database();
var ref = db.ref("someref");
return ref.orderByChild("somechild").equalTo("something").once("value");
}