I've got a https onRequest cloud function that writes a doc. That doc triggers another cloud function, which adds a field to the doc based on a calculation.
Any suggestions how to make the res of the https onRequest function to wait until that field is added and to respond to the https with the value of that field?
exports = module.exports = functions.https.onRequest(async (req, res) => {
const user = await admin.firestore().collection("users").doc();
const enquiryId = await admin.firestore().collection("users").doc(user.id).collection("enquiries").doc();
await enquiryId.set({
value: req.query.value;
});
let getQ = functions.firestore.document('users/{user.id}/enquiries/{enquiryId.id}').onUpdate((change, context) => {
const newValue = change.after.data();
q = newValue.quote;
return q
});
getQ;
return res.status(200).send(getQ);
});
Update following your comments below:
Since the "cloud function gets triggered when we write to the firestore from mobile or web" you could set up a listener to the users/{user.id}/enquiries/{enquiryId.id} document.
You just have to send back the values of user.id and enquiryId.id as response to the HTTP Cloud Function, as follows:
await enquiryDocRef.set(data);
return res.status(200).send({userDocID : user.id, enquiryDocId: enquiryId.id});
and you use these two values to set the listener. The doc is here: https://firebase.google.com/docs/firestore/query-data/listen
This way, when reaching step 4 described in your comment, i.e. when "based on the calculation, a field in the (users/{user.id}/enquiries/{enquiryId.id}) doc gets updated", your listener will be triggered and you will get this updated field value in your front-end.
Initial answer:
If you want the HTTP Cloud Function to "wait until that field is added and to respond to the https with the value of that field", you just have to wait that the asynchronous set() operation is completed before sending back the response.
You can detect that the operation is completed when the Promise returned by the set() method resolves.
Since you do await enquiryId.set({...}) you are therefore very close to the solution: await will make the Cloud Function wait until that Promise settles (is not pending anymore), so you just have to return the response right after this line, as follows:
Therefore just do as follows:
exports = module.exports = functions.https.onRequest(async (req, res) => {
try {
const userDocRef = await admin.firestore().collection("users").doc();
const enquiryDocRef = await admin.firestore().collection("users").doc(userDocRef.id).collection("enquiries").doc();
const data = {
value: req.query.value
};
await enquiryDocRef.set(data);
return res.status(200).send(data);
} catch (error) {
return res.status(400).send(error);
}
});
Related
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 11 months ago.
I have a patch endpoint that takes a body of fields to update, iterates over each field, and updates the correct value. I want to do something send a response once all those things are done, but I do not know how to await a loop of operations. If it was a singular operation I could just add .then(), but that would not work in this case. What is an elegant solution to this?
Code:
const updateUser = (req, res) => {
const db = mongoConnection.getDb();
for (key in req.body) {
db.collection('users').updateOne(
{username: req.query.username},
{$set: {[key]: req.body[key]}}
)
}
// send response once for loop is done
}
You could mark the outer function async and await each DB update inside the loop. Then you'll know that after the loop completes, all DB updates are done.
An even better way is to run the updates in parallel since they do not depend on each other. You can use Promise.allSettled() which takes an array of promises and resolves when the last one is finished.
const updateUser = async (req, res) => {
const db = mongoConnection.getDb();
const dbUpdates = Object.entries(req.body).map((key, value) => {
return db.collection('users').updateOne(
{ username: req.query.username },
{ $set: { [key]: value }}
);
});
await Promise.allSettled(dbUpdates);
// Send response, at this point all DB updates are done
};
I think an easy answer would be hiding the for loop in an async function, then use it in the endpoint.
const updateUserField = async () =>
db.collection("users").updateOne(
{
username: req.query.username,
},
{
$set: { [key]: req.body[key] },
}
);
const updateUserEndpoint = (req, res) => {
for (let field of req.body) {
await updateUserField(field);
}
res.status(200).send('updated');
}
Then, for testing, you could wrap the endpoint with a function that injects the function itself:
const updateUserEndpoint = (userUpdateFunc) => userUpdateFunc(req, res);
This pattern is called dependency injection - when writing unit tests for the endpoint, you'd just replace userUpdateFunc with whatever mocked function you want to use. This removes the need to monkeypatch the function in the test.
I have a async function like this:
exports.myFunction = async (req, res, next) => {
if (some condition) {
next()
}
try {
const results = await axios.get(`https://a-domain.com/url/path`);
const info = results.data;
const docRef = await addDoc(collection(db, "name_of_collec"), info);
console.log("Document written with ID: ", docRef.id);
} catch (e) {
console.error("Error adding document: ", e);
}
next();
};
Here, response from the first request is required in the second request. But according to my knowledge since I am making asynchronous requests, the two requests should be made at the same time.
What I cannot understand:
So, how it is possible that there is no error occurring?
How is it that, sometimes the no document is added to Firestore? Like it is skipped somehow...
What modification can be done so that await addDoc(collection(db, "name_of_collec") executes compulsorily?
Context info:
I am using Express.js and Node.js to make a backend
I am using Axios to make the first request and the second one is adding a document to Firestore.
Firestore is a NoSQL database inside Google's Firebase.
But according to my knowledge since I am making asynchronous requests,
the two requests should be made at the same time.
asynchronous doesn't mean that the requests are made at the same time. It means the code won't wait for the responses from these requests. Requests are made in the manner you add them in the code and responses will be asynchronous i.e the response will be received at some point in future time.
And there is no guarantee in what manner they will be received.
However, when you sue async/await, the code execution waits at await keyword for the response based on its success or failure it calls the next flow. It is nothing but syntactic sugar on promises. Under the hood, this is how your code will be executed:
exports.myFunction = async (req, res, next) => {
if (some condition) {
next()l
}
axios.get(`https://a-domain.com/url/path`).then(result => result).then(data => {
const info = data;
addDoc(collection(db, "name_of_collec"), info).then(response => {
console.log("Document written with ID: ", docRef.id);
}).catch(e => {
console.error(e);
})
}).catch(e => {
console.error(e);
})
next();
};
So, how it is possible that there is no error occurring?
It is not necessary that the error occurs when you request. If that is the case catch handler should be activated.
How is it that, sometimes the no document is added to Firestore? Like
it is skipped somehow...
The reason might be info is an empty object and firestore automatically removes the empty document.
There is no issue, but there is not data info that is empty sometimes that skips the addition(it does request firestore, but firestore removes the empty docs)
exports.myFunction = async (req, res, next) => {
if (some condition) {
next()
}
try {
const results = await axios.get(`https://a-domain.com/url/path`);
const info = results.data;
const is_Info_Data = Array.isArray(info) ? info?.length >= 1: Object.keys(info) !== 0
if (is_Info_Data) {
const docRef = await addDoc(collection(db, "name_of_collec"), info);
console.log("Document written with ID: ", docRef.id);
} else {
console.log(`Info is empty ${info} no addition to DB`);
}
next();
} catch (e) {
console.error("Error adding document: ", e);
next(e);
}
};
I have a Vue.js app running on Firebase and as a database I use Firestore. The app has to import data (clientes) form another app (app2), but app2 only exports by sending an XML code through POST to an address. To receive the POST from app2, my app utilizes Firebase Cloud Functions.
const xml2js = require("xml2js");
const functions = require("firebase-functions");
const cors = require("cors");
const express = require("express");
const app = express();
const admin = require("firebase-admin");
const db = admin.initializeApp().firestore();
function parseXml(xml) {
return new Promise((resolve, reject) => {
xml2js.parseString(xml, { explicitArray: false }, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
app.use(cors({ origen: true }));
app.post("/", async (request, response) => {
let xml = request.body.XMLRecords.toString();
const clientes = db.collection("clientes");
xml = xml.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, "&").trim();
xml = " " + xml;
console.log("Prep XML:");
console.log(xml);
let parsXml = await parseXml(xml);
console.log("parsXML");
console.log(parsXml);
Array.from(parsXml.Records.Clientes.Cliente).forEach(async cli => {
if (cli !== null) {
delete cli.$;
const docRef = clientes.doc(cli.CliCodigo);
console.log("cli " + cli.CliCodigo);
console.log(cli);
const writeResult = await docRef.set(cli);
console.log("Cliente " + cli.CliCodigo + " salvo");
}
});
response.send("Received.");
});
exports.apiClientes = functions.https.onRequest((request, response) => {
if (!request.path) {
request.url = `/${request.url}`; // prepend '/' to keep query params if any
}
return app(request, response);
});
The app does receive the request and is able to process it, but when I try to comunicate with the Firestore database the function stops sending the console.log()staments and won't save the data to the database.
What am I doing wrong?
This is most probably because within your Array.from(parsXml.Records.Clientes.Cliente).forEach() loop you are executing several asynchronous set() operations but you are returning the response (response.send("Received.");) before those asynchronous operations are done.
By doing response.send("Received."); you are indicating to the instance running your Cloud Function that it can terminate it, but in most cases the asynchronous writes to Firestore are not completed.
You need to correctly handle the Promises returned by the set() method calls, as follows (untested):
//....
const promises = [];
Array.from(parsXml.Records.Clientes.Cliente).forEach(cli => {
if (cli !== null) {
delete cli.$;
const docRef = clientes.doc(cli.CliCodigo);
promises.push(docRef.set(cli));
}
});
await Promise.all(promises);
response.send("Received.");
//....
So, we use Promise.all() to execute in parallel all the set() method calls. Promise.all() method returns a single Promise that fulfills when all of the promises passed to as an iterable (i.e. the promises array) have been fulfilled. Therefore you are sure that all the asynchronous work is done when you send back the response, indicating to the Cloud Function platform that it can safely terminate your Cloud Function.
I am new to await/async in Jquery. When I am trying to use it in my js file, It is getting executed, but not as expected.
async updateProduct (product) {
product.travelers.forEach((traveler, travelerOrder) => this.updateProductwithPassengerName(traveler) )
return product
}
async updateProductwithPassengerName (traveler) {
const travelerId = traveler.profile_id;
var body = await this.userServiceClient.request('/v2/users/' + travelerId + '/info','GET')
traveler.first_name = body.first_name
traveler.last_name = body.last_name
return traveler
}
async request (path, method, body) {
const options = {
method: method,
body: body,
headers: this.headers(),
credentials: 'same-origin'
}
const response = await fetch(this.relativeUrl(path), options)
if (!response.ok) {
throw new HttpError('Technical error occured', response.status)
}
return response.json().catch(function () {
throw new HttpError('No results found', response.status)
})
}
Those are the 3 functions. What is happening now is that
traveler.first_name = body.first_name
traveler.last_name = body.last_name
these are not setting in synchronous manner( after
var body = await this.userServiceClient.request('/v2/users/' +
travelerId + '/info','GET')
. These are executing after a quiet a long time.
What I am doing here is that, for each traveler I am setting first_name and last_name. And updating the product object. Since this setting of values is happening after a long time, product object is getting updated later, by the time UI page renders. I want this setting values to happening before doing anything else in jquery.
Looking for help
The problem is that forEach will not await the asynchronous result and so your first function returns a promise that immediately resolves, without waiting for the requests to finish.
Here is how to correct:
async updateProduct (product) {
await Promise.all(product.travelers.map((traveler, travelerOrder) => this.updateProductwithPassengerName(traveler) ));
return product
}
Or, if the backbone cannot deal with all these concurrent requests, then wait for each of them to complete before issuing the next:
async updateProduct (product) {
for (let traveler of product.travelers) {
await this.updateProductwithPassengerName(traveler);
}
return product
}
when using asyn/await in JQuery or angularjs or nodejs, use promise. Promise will complete the process and then return either success or fail. Then only execute next steps.Go through below links and you will get idea.
Modern Asynchronous JavaScript with Async and Await
async & await in Javascript
I am trying to query mssql database to get password for the user, that is sending post request and then just show it in console. But the promise I've made isn't working the way I wanted to and the password isn't loaded into the variable. Here's my code:
app.post('/test', function () {
let user = 'User';
let query = 'SELECT [Password] as password FROM [Table] where [User] = ' + SqlString.escape(user);
let password = (async function () {
try {
let pool = await sql.connect(dbConfig);
let result = await pool.request()
.query(querys);
console.dir(result.recordset[0].password) //Value here is OK
return result.recordset[0].password
} catch (err) {
// ... error checks
}
})()
console.log(password); //here I am getting "Promise { pending }"
});
The result I get is: Promise { pending }
'DatabasePassword'
Here is an example of using async / await with express.
Express doesn't handle the errors, but that's no big problem you can handle them yourself, you could even wrap into a generic error handler to make things easier.
Anyway here is your code modified to use async/await in express.
app.post('/test', async function (req, res) {
try {
const user = 'User';
const query = 'SELECT [Password] as password FROM [Table] where [User] = ' + SqlString.escape(user);
const pool = await sql.connect(dbConfig);
const result = await pool.request()
.query(querys);
const password = result.recordset[0].password;
console.log(password);
res.end(password);
} catch (e) {
res.end(e.message || e.toString());
}
});
Had the same problem while using the MongoDB database, just made the post request async by adding keyword async to the function:
app.post('/register', async function(req,res){
const myData = {username: req.body.name}
const doesUserExists = await UserModel.exists(myData)
})
Functions marked as async returns a promise. That is a wrapper around a future resolution (value or error). A function that returns a promise (which async functions does automatically) either has to be resolved as a promise by chaining .then(..) or by "unwrapping" it with await.
A function that somewhere in its code awaits an async function needs to be async itself. That means that whatever calls that function needs to await it or resolve it as a promise and so forth.