Async post function in NodeJS - 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.

Related

Why does the Javascript function return right away even with await?

I'm newer to JavaScript and struggling to understand why this function just returns right away, even though I added an await. The code below is the minimal example to replicate what I am attempting to do. When I call add_user() from add(), id is undefined because add_user() is returning right away instead of waiting for completion of the query. I added the console log to verify that row.id is the value I expected (i.e. 1).
'use strict';
import sqlite3 from 'sqlite3'
import { open } from 'sqlite';
async function add_user(value) {
await (async () => {
const db = await open({
filename: dbFile,
driver: sqlite3.Database
})
const row = await db.get(`SELECT id FROM Users WHERE name LIKE "${value}"`)
console.log(row.id)
return row.id
})()
}
async function add(req, res) {
var id = await add_value(req.body.value)
console.log(id)
}
I'm pretty sure the code is running asynchronously as desired - it's just that you aren't returning anything from add_user. For a smaller example:
async function someFn() {
await somePromise;
}
Above, someFn will always return a Promise that resolves to undefined, because nothing was returned from someFn. You're running into the same issue.
Use instead
async function add_user(value) {
const db = await open({
filename: dbFile,
driver: sqlite3.Database
})
const row = await db.get(`SELECT id FROM Users WHERE name LIKE "${value}"`)
console.log(row.id)
return row.id
}

Simple Firestore query doesn't give me complete documents in a collection

My Firestore data model looks like this
Organizations/{orgId}/cloudAccounts/{cloudAccountId}/resources/{resourceId}
I am running a simple query like this
let documents = await db.collection("Organizations")
.doc("orgIdabc")
.collection("cloudAccounts")
.doc("cloudAccountIdabc")
.collection("resources")
.get();
console.log("LENGTH: ", documents.docs.length);
Now the problem is that when I try to log the document length, it's always giving me a different length. I'll share the output as well.
LENGTH: 18
LENGTH: 19
LENGTH: 19
LENGTH: 6
LENGTH: 3
LENGTH: 19
LENGTH: 12
LENGTH: 19
LENGTH: 19
Now the actual length is 19, but as you can see I am getting different lengths.
I've no idea what's the problem here, any help would be really appreciated.
Updated
as per request I am adding the complete code snippet
Also I've come to know that this problem is only happening on Admin SDK and not the client SDK
const { db } = require("./firestore");
const functions = require("firebase-functions");
exports.runScanOnAllCloudAccounts = functions.https.onRequest((req, res) => {
runScanOnAllCA();
return res.status(200);
});
async function runScanOnAllCA() {
try {
for (let i = 0; i < 10; i++) {
let documents = await db.collection("Organizations")
.doc("orgIdabc")
.collection("cloudAccounts")
.doc("cloudAccountIdabc")
.collection("resources")
.get();
console.log("LENGTH: ", documents.docs.length);
}
} catch (err) {
console.log("err: ", err);
}
}
Update 02 ===========>
I've updated the code to use Promise() heavy approach, as recommended by some users and I still get different document lengths.
I feel that people are missing the point here, Promises are just a way to run asynchronous code, we can resolve this by using async await that was already being used in the previous version of the code.
Still the following code snippet doesn't solve the problem.
const { db } = require("./firestore");
const functions = require("firebase-functions");
exports.runScanOnAllCloudAccounts = functions.https.onRequest(
async (req, res) => {
runScanOnAllCA(res)
.then(resolve => {
console.log(resolve);
})
.catch(err => {
console.log(err);
});
// return res.status(200);
}
);
async function runScanOnAllCA(res) {
return new Promise(async (resolve, reject) => {
db.collection("Organizations")
.doc("sumair-hello-world_axtr8")
.collection("cloudAccounts")
.doc("4ZQgjt94pvEQTlvxSJ75")
.collection("resources")
.get()
.then(querySnapshot => {
resolve(querySnapshot.docs.length);
})
.catch(err => {
reject(err);
});
});
}
You should use the Admin SDK in order to interact with Firestore from a Cloud Function.
Secondly, it is known that using await in loops can lead to "erratic" results. See, for example, https://www.google.com/search?client=firefox-b-d&q=for+and+await
Finally, note that you are incorrectly calling your asynchronous runScanOnAllCA() function. You should either use then() or make your Cloud Function async and use await, see the code below.
You should adapt your CF as follows for using the Admin SDK:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.runScanOnAllCloudAccounts = functions.https.onRequest(async (req, res) => {
await runScanOnAllCA(); //<--- Note the await here and the async above, since you are calling an async function
res.status(200); //<--- No need to use return in an HTTPS Cloud Function, just terminate it with res.redirect(), res.send(), or res.end().
});
async function runScanOnAllCA() {
try {
const db = admin.firestore();
//The loop was removed. Use another way if needed, e.g. Promise.all() or the techniques presented in the links above.
let documents = await db.collection("Organizations")
.doc("orgIdabc")
.collection("cloudAccounts")
.doc("cloudAccountIdabc")
.collection("resources")
.get();
console.log("LENGTH: ", documents.docs.length);
} catch (err) {
console.log("err: ", err);
}
}

Using Cloud Functions to store data form an external API on Firestore database

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.

Listening to update within a function

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);
}
});

Node.js mssql return recordset

I'm new to learning Node.js, so I'm still getting used to asynchronous programming and callbacks. I'm trying to query a MS SQL Server database and return the recordset to display in my view.
The mssql query is working correctly when printed to console.log. My problem is not knowing how to properly return the data.
Here is my mssql query:
var config = require('../../db/config');
async function getJobList(activeJD) {
const sql = require('mssql')
let sqlResult = '';
try {
await sql.connect(config)
const result = await sql.query(`select top 10 [jobid], [title] from OrgJobs where ActiveJD = ${activeJD}`);
console.log(result); // working correctly
sqlResult = result;
} catch (err) {
// ... error checks
}
return sqlResult;
}
module.exports = getJobList;
Using express, I'm trying to have my route call this sql query and pass it's result to my view. Here is my route code:
const express = require('express');
//....
const app = express();
//....
app.get('/jds', (req, res) => {
const getJobList = require("../models/jds/list");
let jobList = getJobList(0);
res.render('jds/index', {
title: appName + ' | Job Descriptions',
header: 'Active Job Descriptions',
data: jobList
});
})
The recordset from getJobList() isn't being returned to jobList. I've read around and I believe this is because getJobList is asynchronous and jobList is being called before getJobList has returned it's value, right? If so, I believe I need to add a callback, but I'm not sure how to implement that into what I already have. Any advice is greatly appreciated!
An async method will always return a Promise. That means that jobList is a Promise.
let jobList = getJobList(0);
You can either use the await syntax to "unpack" and get sqlResult from the promise (you would need to make your callback method async to do this), or simply use .then to get the return value from your promise (ie: sqlResult):
app.get('/jds', (req, res) => {
const getJobList = require("../models/jds/list");
const jobList = getJobList(0); // jobList is a promise
jobList.then(result => { // result is sqlResult
res.render('jds/index', {
title: appName + ' | Job Descriptions',
header: 'Active Job Descriptions',
data: result
});
});
})

Categories