I have a function that returns the result correctly with: response.send("Update Last Payments Completed"); but in the log it reports: and no documents are updated
Error: Process exited with code 16
Here is my code:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
/// Updates the last payment done in the neighbors documents
export const updateLastPaymentHTTP = functions.https.onRequest(
async (request, response) => {
try {
const neighbors = await admin.firestore().collection("neighbors").get();
const promises = [];
neighbors.forEach(async (neighbor) => {
const topPayment = await admin
.firestore()
.collection(`neighbors/${neighbor.ref}/payments`)
.orderBy("date", "desc")
.limit(1)
.get();
topPayment.forEach(async (payment) => {
if (payment.exists) {
const lastPayment = payment.data().date;
promises.push(neighbor.ref.update({ last_payment: lastPayment }));
} else {
promises.push(neighbor.ref.update({ last_payment: null }));
}
});
await Promise.all(promises);
response.send("Update Last Payments Completed");
});
} catch (error) {
console.log(`Error Updating Last Payment and Debt ${error}`);
}
}
);
Thanks in advance
You're calling response.send() inside a loop. That's almost certainly not what you want, as you can only send a single response, and then the function terminates. Move the last await and response.send() outside the loop. to be executed only a single time after all the work is complete.
neighbors.forEach(async (neighbor) => {
// ...
});
await Promise.all(promises);
response.send("Update Last Payments Completed");
Related
I have a test file like this.
const { silBastan } = require("../database.js");
const axios = require('axios').default;
describe("authentication", () => {
describe("when data schema is valid", () => {
test("returns 201 response code if the user doesnt already exists", async () => {
await silBastan();
const response = await axios.post('http://localhost:8000/auth/register', {
email: "my_email",
password: "1234"
});
expect(response.status).toBe(201);
});
});
});
And silBastan is defined here like this
const pg = require("pg");
const client = new pg.Client();
async function silBastan() {
return await client.query(`DELETE FROM account`);
}
Of course i made sure the server started and connected to the database before running the tests.
I wondered if there is something wrong with silBastan and tested it inside a express route handler like this
router.post('/register', async (req, res) => {
const { email, password } = req.body;
await silBastan();
try {
await db.createAccount(email, password);
res.sendStatus(201);
} catch (e) {
res.status(400).json({ err: "Already exists" });
}
});
and there was no timeout. And after this i returned another promise from silBastan like this:
async function silBastan() {
// return await client.query(`DELETE FROM account`);
return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
And again there is no timeout. I tried couple of other variations as well like these:
function silBastan() {
return client.query(`DELETE FROM account`);
}
async function silBastan() {
await client.query(`DELETE FROM account`);
}
Nothing worked i always get this message:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
I don't think the problem is with the function because i would get the same behavior in the route handler too.
COIN LIST is an array of crypto coins(["BTCUSDT",...]). I try to get the price using getPriceAction and RSI from getRSI and these two functions are working when I try to console DATA. But when I try to print the response after the completion of the loop. It prints the empty array and the length is 0 of this array. I want to store the DATA object (consisting of SYMBOL, closing price and RSI) as an element in the response array
import { COIN_LIST } from "./COIN_LIST.js";
import { getPriceAction } from "./PRICE_ACTION.js";
import { getRSI } from "./RSI.js";
async function main() {
try {
let response = await [];
await COIN_LIST.forEach((element, i) => {
setTimeout(() => {
let data = { symbol: element };
getPriceAction(element, "4h").then((res) => {
data.closingPrice = res;
getRSI(res).then((res) => {
data.RSI = res.reverse();
data.closingPrice = data.closingPrice.reverse();
response.push(data);
console.log(data)
});
});
}, i * 1000);
});
console.log(response);
} catch (error) {
console.log(error.message);
}
}
main();
If you want to use async/await properly for your code, then use async/await, don't use .then/.catch as well
Some notable changes
there is no setTimeout of increasing seconds ... just waiting 1 second after one result before getting the next - far cleaner, and if one request happens to take a lot longer, you won't end up with two requests at once (which may be an issue if the API is rate limited)
no .then ... use async/await OR .then/.catch - very rare to need both in the one function
don't use forEach with async/await ... it never does what you want, and creating an array of Promises inside a .forEach is extremely naive, you may as well use .map instead! then you can await Promise.all(xxx.map(.....)) - but that's useful for concurrent requests, not so much for serial requests like your code does
import { COIN_LIST } from "./COIN_LIST.js";
import { getPriceAction } from "./PRICE_ACTION.js";
import { getRSI } from "./RSI.js";
async function main() {
try {
const wait = (ms) => new Promise(resolve => setTimeout(resolve, 1000));
let response = []; //---> don't need that `await`
for (let element of COIN_LIST) {
let data = { symbol: element };
data.closingPrice = await getPriceAction(element, "4h");
const res = await getRSI(data.closingPrice);
data.RSI = res.reverse();
data.closingPrice = data.closingPrice.reverse();
response.push(data);
console.log(data);
await wait(1000);
}
console.log(response);
} catch (error) {
console.log(error.message);
}
}
main();
the await wait(1000) could be tweaked depending on the rate limiting of the API ... if the rate limit applies to when the request is made, you could make a function that is smart about the delay between requests.
The code this way assumes the rate limit is based on the period between previous response to next request.
After the completion of the loop, the promises didn't get resolved yet, that's why it print an empty array.
One way to achieve what you need is using await for(...), or wait for all promises to be resolved, and then print the results.
import { COIN_LIST } from "./COIN_LIST.js";
import { getPriceAction } from "./PRICE_ACTION.js";
import { getRSI } from "./RSI.js";
async function main() {
try {
let response = []; //---> don't need that `await`
const promises = []; //---> array of promises
COIN_LIST.forEach((element, i) => {
setTimeout(() => {
let data = { symbol: element };
const promise = getPriceAction(element, "4h").then((res) => {
data.closingPrice = res;
getRSI(res).then((res) => {
data.RSI = res.reverse();
data.closingPrice = data.closingPrice.reverse();
response.push(data);
console.log(data)
});
});
promises.push(promise) //---> save the reference to a promise
}, i * 1000);
});
await Promise.all(promises) //---> await for all promises to be resolved, then print the result
console.log(response);
} catch (error) {
console.log(error.message);
}
}
main();
What I am trying to accomplish in a Firebase Function:
Read from Firebase database email addresses to batch email.
Loop through each one and send out emails.
I am having an issue with closing promises I believe. These don't need to be ran in order, I just need to resolve all promises before ending.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const https = require('axios');
exports.sendEmail = functions.pubsub.topic('nightly_topic').onPublish(() => {
let emailsToSend = [];
async function getTests (){
admin.firestore()
.collection("tests")
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
emailsToSend.push(doc.data())
});
})
.catch(function (error) {
console.error(error);
});
}
async function send (address){
let body = {
//MANDRILL INFO REMOVED
};
let endpoint = 'https://mandrillapp.com/api/1.0/messages/send-template.json';
https.post(endpoint, body)
.then((result) => {
console.log('SUCCESS');
})
.catch(function (error) {
console.error(error);
});
}
async function init() {
await getTests();
for (const email of emailsToSend) {
await send(email.address);
}
}
init();
});
You're missing return statements from your functions.
return https.post(... etc
Try this:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const https = require('axios');
exports.sendEmail = functions.pubsub.topic('nightly_topic').onPublish(() => {
let emailsToSend = [];
function getTests (){
return new Promise((resolve, reject) => {
admin.firestore()
.collection("tests")
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
emailsToSend.push(doc.data())
});
resolve(emailsToSend);
})
.catch(function (error) {
reject(error)
});
});
}
async function send (address){
let body = {
//MANDRILL INFO REMOVED
};
let endpoint = 'https://mandrillapp.com/api/1.0/messages/send-template.json';
https.post(endpoint, body)
.then((result) => {
console.log('SUCCESS');
})
.catch(function (error) {
console.error(error);
});
}
async function init() {
const emailsToSend = await getTests();
for (const email of emailsToSend) {
await send(email.address);
}
}
init();
});
It might help you.
so you are almost there. The issue is that you are not returning anything. You should fix it by just doing:
Return Promise
// code ...
exports.sendEmail = functions.pubsub.topic('nightly_topic').onPublish(() => {
// code
return init();
}
Use async
// code ...
exports.sendEmail = functions.pubsub.topic('nightly_topic').onPublish(async () => {
// code
await init();
}
Note: an async function always returns a Promise.
Suggestion
In your code, you are sending emails one at a time. await send(email.address); This line of code waits until the email is sent, before sending the next one, which is not efficient.
My suggestion is sending all emails at the same time and return a promise that resolves when every email is sent. That should look something like this:
//change this
for (const email of emailsToSend) {
await send(email.address);
}
// --------------into this--------------------------------
//This is an array of promises
const promises = emailsToSend.map(email => {
return send(email.address);
});
await Promise.all(promises);
Hope that helps :)
I have made a function to check if a certain thing already exists in the database. I have simply copy-pasted the logic I'm using to get something in the database and changed the query object + what is returned. But now it seems that node doesn't like that and just throws an error that makes no sense to me.
Where I call the function:
let exists = await queryDatabaseExists(uniqueQuery, res);
The function that I'm calling:
async function queryDatabaseExists(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.recordset.rowsAffected[0] = 1){return true} else { return false }
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
The error that I'm getting:
let exists = await queryDatabaseExists(uniqueQuery, res);
^^^^^
SyntaxError: await is only valid in async function
ALL code for that route:
router.post("/admin/category", (req, res) => {
uniqueQuery = `SELECT [name] from [dbo].[idtTV_categories] WHERE [name] = '${req.body.name}'`
getQuery = `SELECT [id]
,[name]
,[description]
,[created_time]
,[created_by] from [dbo].[idtTV_categories]`
standardQuery = `INSERT INTO [dbo].[idtTV_categories] ([name],[description],[created_time],[created_by])
VALUES
('${req.body.name}',
'${req.body.description}',
SYSDATETIME(),
'${req.user.name}')`;
let exists = checkIfExists();
function checkIfExists() { result = await queryDatabaseExists(uniqueQuery, res); return result} ;
console.log(exists);
if(req.user.roles.some(role => role === admin || role === editor)){
if(!existsInDatabase){
if(queryDatabase(standardQuery, res)){queryDatabase_get(getQuery, res)}
}
}
else { res.statusMessage = `${req.user.name} is not authorized to add categories.`;
console.log(req.user.roles)
res.status(520).send() };
})
All functions being called:
///////////// MAIN QUERYING FUNCTION //////////////////////
async function queryDatabase_get(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
res.send(result.recordset);
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
async function queryDatabaseExists(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.recordset.rowsAffected[0] = 1){return true} else { return false }
} catch (err) {
res.status(520).send();
}
}
async function queryDatabase(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.rowsAffected > 0){ return true }
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
it must be inside async.
ex:
app.post('/', async (req, res) => {
let exists = await queryDatabaseExists(uniqueQuery, res);
});
This means that the function in which the call to queryDatabaseExists is performed must also be async, in order to use the await keyword inside it. The queryDatabaseExists function itself looks correct.
queryDatabaseExists function need to return promise if not you cannot use await
await command expect a promise to be return by the function link
let exists = await queryDatabaseExists(uniqueQuery, res);
await can only be used in async function. For your scenario you wanted wait for result from uniquequery. First you have to make change in your router.post callback like router.post('/url',async(req,res)=>{}); for making a synchornus call to checkifexist function. Second in order to use await in checkifexist function you have to make changes to checkisexist function to async function checkifexist(){}. Thirdly you wanted to wait for DB response for that you have use await while calling checkifexist function --> let result=await checkifexist(). you can check MDN website for better understanding.
router.post('url',async(req,res)=>{// in order to use await in checkifexist
//your rest of the code.
let result=await checkifexist();// waiting for db result
async function checkifexist(){//your awaited code.}
console.log(result);
});
I'm trying to use async/await with transaction.
But getting error "Argument "updateFunction" is not a valid function."
var docRef = admin.firestore().collection("docs").doc(docId);
let transaction = admin.firestore().runTransaction();
let doc = await transaction.get(docRef);
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await transaction.update(docRef, { likes: newLikes });
The above did not work for me and resulted in this error: "[Error: Every document read in a transaction must also be written.]".
The below code makes use of async/await and works fine.
try{
await db.runTransaction(async transaction => {
const doc = await transaction.get(ref);
if(!doc.exists){
throw "Document does not exist";
}
const newCount = doc.data().count + 1;
transaction.update(ref, {
count: newCount,
});
})
} catch(e){
console.log('transaction failed', e);
}
If you look at the docs you see that the function passed to runTransaction is a function returning a promise (the result of transaction.get().then()). Since an async function is just a function returning a promise you might as well write db.runTransaction(async transaction => {})
You only need to return something from this function if you want to pass data out of the transaction. For example if you only perform updates you won't return anything. Also note that the update function returns the transaction itself so you can chain them:
try {
await db.runTransaction(async transaction => {
transaction
.update(
db.collection("col1").doc(id1),
dataFor1
)
.update(
db.collection("col2").doc(id2),
dataFor2
);
});
} catch (err) {
throw new Error(`Failed transaction: ${err.message}`);
}
IMPORTANT: As noted by a couple of the users, this solution doesn't use the transaction properly. It just gets the doc using a transaction, but the update runs outside of it.
Check alsky's answer. https://stackoverflow.com/a/52452831/683157
Take a look to the documentation, runTransaction must receive the updateFunction function as parameter. (https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore#runTransaction)
Try this
var docRef = admin.firestore().collection("docs").doc(docId);
let doc = await admin.firestore().runTransaction(t => t.get(docRef));
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await doc.ref.update({ likes: newLikes });
In my case, the only way I could get to run my transaction was:
const firestore = admin.firestore();
const txRes = await firestore.runTransaction(async (tx) => {
const docRef = await tx.get( firestore.collection('posts').doc( context.params.postId ) );
if(!docRef.exists) {
throw new Error('Error - onWrite: docRef does not exist');
}
const totalComments = docRef.data().comments + 1;
return tx.update(docRef.ref, { comments: totalComments }, {});
});
I needed to add my 'collection().doc()' to tx.get directly and when calling tx.update, I needed to apply 'docRef.ref', without '.ref' was not working...