Node.js combining await/async with promises - javascript

I connect to a Postgres database in Node and I get a return value from the database. However, when I try to pass that value to another function, the value is undefined. Check out this code:
async function doDbCall(queryString) {
await client.connect()
.then(async () => {
await client.query(queryString)
.then(dbRes => {
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
})
});
}
Here is the calling function:
async function testTheDb() {
try {
const myReturnVal = await dbConn.doDbCall("SELECT NOW() as now");
console.log("db val:", myReturnVal);
}
catch(ex) {
console.log("db err", ex.message);
}
}
In the console.log in doDbCall() on the query.then success, I get a value in dbRes.rows[0]. The console.log shows dbDoCall - dbRes { now: 2022-09-26T16:47:14.465Z }. So I return that value.
However, in the testTheDb() function, I have another console log where I log the value of myReturnVal. The result of that log shows db val: undefined.
My question is this: Why is myReturnVal null in testTheDb()?

You should not mix async/await syntax with .then() calls. Your problem is that neither the doDbCall function nor the async () => {…} callback do return anything, your only return keyword is nested inside a nested then callback.
You should write either
function doDbCall(queryString) {
return client.connect()
.then(() => {
return client.query(queryString);
})
.then(dbRes => {
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
})
.finally(() => {
client.end();
});
}
or
async function doDbCall(queryString) {
try {
await client.connect()
const dbRes = await client.query(queryString);
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
} finally {
client.end();
}
}

Related

Function returns a pending promise, but value of variable is returned into it

This is the code
function readNotes(){
return new Promise((resolve, reject) => {
user = firebase.auth().currentUser.uid
ref = database.ref("users/" + user + "/notes")
ref.get().then( function(snapshot) {
if (snapshot.exists()) {
keys= Object.keys(snapshot.val())
console.log(keys)
resolve(keys);
} else {
console.log("No data available");
}
}).catch((error) => {
reject(error);
})
})
}type here
I tried calling the function in the console and I was expecting for it to return a fulfilled promise, especially because I was able to log the value of the keys variable
Your function returns Promise. (hence return new Promise(...) at the beginning of your function);
You will need to either await (if you are inside an async function) like so:
const notes = await readNotes();
console.log(notes);
or you can use .then() like so:
readNotes().then(notes => {
console.log(notes)
}).catch(err => {
console.error(error);
}
Note: do not forget .catch to intercept errors.
If you are not in async function, but want to use async/await you can use an IIFE (Immediately Invoked Function Expression).
(async () => {
const notes = await readNotes();
console.log(notes);
})()

Why is JS redis looping more times after succesful write?

I have the following logic for reading and writing redis state
export async function updateRedis() {
let stateName = 'stateName'
try {
let isSuccess = false
while (!isSuccess) {
try {
await redis
.watch(stateName, function (err) {
if (err) {
logger.error(`Error in watch state: ${err}`)
}
redis.get(stateName, function (err, result) {
if (err) {
logger.error(`Error in get state: ${err}`)
}
let state = JSON.parse(result)
// do some processing
redis.multi()
.set(stateName, JSON.stringify(state))
.exec(function (err, result) {
if (err) {
logger.error(`Error in set state: ${err}`)
}
if (result != null) {
isSuccess = true
}
})
console.log(`isSuccess for ${stateName} `, isSuccess)
})
})
} catch (e) {
logger.error(`Error: ${e}`)
}
}
} catch (e) {
logger.error(`Error: ${e}`)
}
return Promise.resolve(true)
}
This will print out
"isSuccess for stateName false"
"isSuccess for stateName true"
"isSuccess for stateName true"
So after the flag changes to true, it will continue for more loops. Sometimes it does more than just once.
Am I doing something wrong?
You cannot mix a synchronous loop (while (!isSuccess) { ... }) with asynchronous functions (redis.watch(stateName, function (err) { ... })).
You can also not await callback-based asynchronous functions. A function must return a promise to be awaitable. Since node-redis gives you promises when you don't pass callbacks to its methods, the key is not to do that (redis.watch(stateName, function (err) { ... }) → redis.watch(stateName)).
Your approach needs to be redone.
Let's make a function that encapsulates a redis transaction with optimistic locking. It takes a connection object, a key, and a value-transforming function, and it returns the result of the .set() operation:
const redisTransaction = async (client, key, transformer) => {
// https://github.com/redis/node-redis/blob/master/docs/isolated-execution.md
return client.executeIsolated(async isolatedClient => {
await isolatedClient.watch(key);
const val = await isolatedClient.get(key);
return isolatedClient.multi()
.set(key, await transformer.call(isolatedClient, val))
.exec();
});
};
Now you can await this function, because it returns a promise. That means we can make a simple infinite loop that exits immediately in case of success (via return), or retries indefinitely.
export async function updateRedis(key, transformer) {
while (true) {
try {
return await redisTransaction(redis, key, transformer);
} catch (err) {
logger.error(`Error for state ${key}: ${err}`);
}
}
}
A transformer function takes a value, and returns a new value. Inside it, the this keyword refers to the isolatedClient from the transaction, which could be useful if your transformation depends on other values from that client.
const result = await updateRedis('stateName', async function (val) {
const state = JSON.parse(val);
const newState = await modifyStateSomehow(state);
return JSON.stringify(newState);
});
The modifyStateSomehow() can itself be an asynchronous (i.e. "promise-returning") function. If it's not, you can make the state transformer a regular function by removing the async and the await.

Best way to use asyn/await in the following code

I am kind of new in Javascript and I want to connect to DB and run a script. Then get the result of script and run functions in order.
If there is any error with one of the functions, it should stop and doesnt run other functions.
I tried the following:
const {
Client
} = require('pg')
const client = new Client({
'connection info'
})
client.connect()
.then(() => console.log('DB connected'))
.catch(err => console.error('connection error', err.stack))
let dbResult
const data = async() => {
try {
dbResult = await client.query('SCRIPT') // array of json
} catch (error) {
console.log(error);
}
}
const func1 = async() => {
try {
// do something with dbResult
console.log('func1 success msg')
} catch (error) {
console.log('error in func1')
}
}
const func2 = async() => {
try {
// do something with dbResult
console.log('func2 success msg')
} catch (error) {
console.log('error in func2')
}
}
const func3 = async() => {
dbResult.forEach(result => {
// do something
})
try {
// do something with dbResult
console.log('func3 success msg')
} catch (error) {
console.log('error in func3')
}
}
data()
func1()
func2()
func3()
All the functions you call are async, therefore return Promises and should be awaited. You can await all of them in a try/catch block, so if one fails, the others won't execute.
Don't use try/catch in each individual function, but rather here :
const data = async() => client.query('SCRIPT') // array of json
const func1 = async() => console.log('func1 success msg')
const func2 = async() => console.log('func2 success msg')
const func3 = async() => dbResult.forEach(result => console.log(result))
(async () => {
try{
await client.connect();
let dbResult = await data();
dbResult = await func1(dbResult);
await func2();
await func3(dbResult);
} catch(err) {
console.log(err);
}
})();
await Promise.all([data, func1, func2, func3]) would also fail if one of the Promises failed, but does not guarantee the execution order.
Below is if you must use try catch inside each of your function body. If not, then I'd stick with the answer from Jeremy above.
What you can do is instead of console logging your errors that you receive in try..catch block, you can throw new error, which will stop the execution of your code and console log the actual error.
(Well, not exactly console log, but rather console.error() it)
This will prevent the execution of other functions, unless you do something with your error (make some error handling where you can execute another code, depending on the error).
In general, the syntax for this as follows:
try {
await someStuff();
} catch (err) {
throw new Error(err)
}
Object err has some additional properties, such as name and message.
Here is more about Error object.

Async function returning undefined in NodeJS

So, I'm new trying to understand how async functions work. Doing it with "Resolve, Reject" from Promises, works fine. But when I try to apply it with async instead of new Promise, for some reason, the function returns undefined. Here is my code :)
note: Sorry for my english, not fluent speaker :)
category = body.category
obtenerCategoria = async(category) => {
Categoria.findOne({ descripcion: category })
.exec((err, categoriaDB) => {
if (err) {
throw new Error(err)
}
if (!categoriaDB) {
return res.status(400).json({
ok: false,
msg: 'Categoria no encontrada'
})
}
console.log(categoriaDB); // Works fine here
return categoriaDB
})
}
crearClase = async() => {
categoria = await obtenerCategoria(category);
console.log(categoria); //getting nothing here
}
crearClase()
.then()
.catch(e => {
return e
})
You don't need to use callback function when you use async/await
Try this code:
obtenerCategoria = async(category) => {
const categoriaDB = await Categoria.findOne({ descripcion: category });
if (!categoriaDB) {
return res.status(400).json({
ok: false,
msg: 'Categoria no encontrada'
})
}
console.log(categoriaDB); // Works fine here
return categoriaDB
}
obtenerCategoria doesn't have a return value. Additionally, you can use await on the Categoria.findOne() call and use try / catch instead of the call to exec(), something like this example.

How to make multiple async await calls

I'm trying to build an object based on the responses from multiple promises, but I'm noticed where only the first promise is actually being worked on while the second is being ignored. What is the best practice to make this work?
if (goldenListKeys[0].name === 'date') {
const date = moment('07-01-2019', 'YYYY-MM-DD').format('MM/DD/YYYY');
_.assign(tmpObj, { inputData: { [goldenListKeys[0].name]: date } });
try {
await this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery })
.catch(error => {
value = error.response['invalid-selection'];
console.log(`One: ${value}`);
});
} catch (err) {}
}
if (goldenListKeys[1].name === 'state') {
console.log('Here');
_.assign(tmpObj, {
inputData: { ...tmpObj, [goldenListKeys[1].name]: 'NC' },
});
try {
await this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery })
.catch(error => {
value = error.response['invalid-selection'];
_.assign(goldenListKeys, { filler: value });
console.log(`Two: ${value}`);
});
} catch (err) {}
}
It appears that you missed one of the fundamental features of async/await.
An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.
The key part being is that the execution inside the function call is paused. So your next if statement won't be considered until after the first promise has resolved.
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
This is usually where I like to use the Promise.all function MDN Link
This is how I would modify your code (I know it doesn't use async/await but, it should accomplish your goal)
function yourFunction(){
let myPromiseArray = [];
if (goldenListKeys[0].name === 'date') {
const date = moment('07-01-2019', 'YYYY-MM-DD').format('MM/DD/YYYY');
_.assign(tmpObj, { inputData: { [goldenListKeys[0].name]: date } });
myPromiseArray.push(his.plansApi
.compileFields({ tmpObj, carrier, benefitQuery }))
}
if (goldenListKeys[1].name === 'state') {
_.assign(tmpObj, {
inputData: { ...tmpObj, [goldenListKeys[1].name]: 'NC' },
});
myPromiseArray.push(this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery }))
}
Promise.all(myPromiseArray).then((resultArray)=>{
//do something with results
}).catch(errorArray => {
//handle Errors
})
}

Categories

Resources