Trying to use await with promise in my code here - how to? - javascript

I've tried but failed in grasping clearly how javascript promises and await work! I somehow managed to cobble together a function that performs what I need in my node.js micro service, but I'm not sure if I'm doing it the right (optimal) way. Also, I achieved what I wanted using promise without await, but also I haven't done any extensive testing of my code to see if it is indeed running exactly the way I think it is. Here is my code that I currently have and works, but I'm not sure if I'm missing using await for proper functioning:
const QryAllBooks = {
type: new GraphQLList(BookType),
args: {},
resolve(){
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err){
reject(err);
}
resolve(results);
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++){
const book_id = json[p].bookid;
const query = `mutation updateShipping
{updateShipping
(id: ${book_id}, input:{
status: "Shipped"
})
{ bookid
bookname }}`
promises.push(apolloFetch({ query }));
}
//I need an await function so that previous apolloFetch
//goes in sequence of bookid, one after the other
Promise.all( promises ).then(( result) => {
errorLogger(27, 'Error', result);
})
.catch(( e ) => {
errorLogger( 29, 'Error', e );
)};
});
});
}
};
module.exports = {
QryAllBooks,
BookType
};

Avoid the Promise constructor antipattern - you should not be doing anything after the call to resolve inside the promise executor. Put all that stuff in a then callback on the new Promise:
resolve() {
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
}).then(results => {
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++){
const book_id = json[p].bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
promises.push(apolloFetch({ query }));
}
return Promise.all(promises);
}).then(result => {
errorLogger(27, 'Result', result);
return result;
}, err => {
errorLogger(29, 'Error', err);
throw err;
)};
}
You can now replace those then calls with await syntax. And also exchange the Promise.all for a sequential awaiting in the loop:
async resolve() {
try {
const results = await new Promise((resolve, reject) => {
// ^^^^^
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
});
const promises = results.map(res => {
const book_id = res.bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
return apolloFetch({ query });
});
const result = await Promise.all(promises);
// ^^^^^
errorLogger(27, 'Result', result);
return result;
} catch(err) {
errorLogger(29, 'Error', err);
throw err;
}
}
async resolve() {
const results = await new Promise((resolve, reject) => {
// ^^^^^
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
});
const fetches = [];
for (let p = 0; p < results.length; p++){
const book_id = results[p].bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
fetches.push(await apolloFetch({ query }));
// ^^^^^
}
return fetches;
}

Related

Async and promise function do not display anything in my res.json call

I have a controller in javascript which should get a given user, and then the pets that are associated with the user. The related pets are stored in an array of object refs within the user schema. At the minute, when I try to res.json the resulting array containing the related pets, it outputs as an empty array '[]'. Following the Mozilla docs and tutorials I have tried to implement a promise on this function to combat my previous issue of the res.json outputting an empty array. I'm not sure where I am going wrong as I am a newbie to JS/express/node/mongo
Problem code:
export const getPetsForAUser = (req, res)=>
{
function getter(){
return new Promise(resolve =>{
User.findOne({_id: req.params._id}, (err, users) =>{
let petlist = users.pets;
for(var i = 0; i < petlist.length; i++){
Pet.findOne({_id:petlist[i]}, (err, pet) =>{
var t = pet
return Promise.resolve(t)
});
}
})
});
}
async function asyncCall(){
const result = await getter();
res.json(result);
}
asyncCall();
};
Using Aync/Await and Promise all
export default async (req, res) => {
const promises = [];
let result = null;
const petlist = await new Promise((resolve, reject) => {
User.findOne({ _id: req.params._id }, (err, users) => {
if (err) {
reject(err);
} else {
resolve(users.pets);
}
});
});
if (petlist && petlist.length) {
for (let i = 0; i < petlist.length; i++) {
// eslint-disable-next-line no-loop-func
const promise = new Promise((resolve, reject) => {
Pet.findOne({ _id: petlist[i] }, (err, pet) => {
if (err) {
reject(err);
} else {
resolve(pet);
}
});
});
promises.push(promise);
}
result = await Promise.all(promises).then((data) => {
console.log('all promises resolved!');
console.log(data);
return data;
});
}
console.log(result);
};
You can implement promises like this in your code:
export const getPetsForAUser = (req, res) => {
return new Promise((resolve, reject) =>{
User.findOne({_id: req.params._id}, (err, users) => {
if (err) reject(err);
let petlist = users.pets;
for(var i = 0; i < petlist.length; i++) {
Pet.findOne({_id:petlist[i]}, (err, pet) =>{
if (err) reject(err);
var t = pet
resolve(t)
});
}
})

async function not getting result

I am trying to get the result of an async function in my calculateOrderAmount function but it returns undefined.
The console.log in the called function returns the good result, but inside calculateOrderAmount, I get undefined. Here is my code:
getMultiStrats = async () => {
await MultiStrats.findOne({}, (err, multiStrats) => {
if (err) {
return err
}
if(!multiStrats) {
return console.log('MultiStrat not found')
}
console.log('returns MultiStrat: ' + multiStrats)
return multiStrats
})
.catch(err => console.log(err))
}
async function calculateOrderAmount(balance, ticker){
const multiState = await StrategyController.getMultiStrats().catch((err) => console.log(err))
console.log('multiState: ' + multiState)
some logic
}
Here is the console log:
multiState: undefined
returns MultiStrat: {
_id: 5ff73c74d1135b39fc709b80,
positionsCount: 1,
inTradeCount: 0,
__v: 0
}
What did I miss? Thanks you very much for your time!
The current approach is pretty unclear--there's no need for .catch, async, await all at once. return multiStrats returns from the inside of the callback, not from getMultiStrats. The async/await on getMultiStrats is superfluous, just adding another promise wrapper that doesn't accomplish anything.
Given that findOne as shown here uses a callback rather than a promise, you can either use callbacks all the way or you can promisify findOne as follows, using .then and .catch in the caller:
const MultiStrats = {
findOne: (obj, cb) => cb(null, "I'm a multistrat!")
};
const StrategyController = {
getMultiStrats: () => new Promise((resolve, reject) =>
MultiStrats.findOne({}, (err, multiStrats) => {
if (err) {
return reject(err);
}
else if (multiStrats) {
return resolve(multiStrats);
}
reject(Error("Multistrat not found"));
})
)
};
const calculateOrderAmount = (balance, ticker) =>
StrategyController
.getMultiStrats()
.then(multiState => {
console.log('multiState: ' + multiState)
// some logic
})
.catch(err => console.error(err))
;
calculateOrderAmount();
Or use async/await and try/catch:
const MultiStrats = {
findOne: (obj, cb) => cb(null, "I'm a multistrat!")
};
const StrategyController = {
getMultiStrats: () => new Promise((resolve, reject) =>
MultiStrats.findOne({}, (err, multiStrats) => {
if (err) {
return reject(err);
}
else if (multiStrats) {
return resolve(multiStrats);
}
reject(Error("Multistrat not found"));
})
)
};
const calculateOrderAmount = async (balance, ticker) => {
try {
const multiState = await StrategyController.getMultiStrats();
console.log('multiState: ' + multiState)
// some logic
}
catch (err) {
console.error(err);
}
};
calculateOrderAmount();
If this is MongoDB's findOne and already returns a promise, then you can just return the promise to the caller, optionally awaiting it and throwing for the null result:
const MultiStrats = {
findOne: async query => "I'm a multistrat!"
};
const StrategyController = {
getMultiStrats: async () => {
const result = await MultiStrats.findOne({});
if (result) {
return result;
}
throw Error("Multistrat not found");
}
};
const calculateOrderAmount = (balance, ticker) =>
StrategyController
.getMultiStrats()
.then(multiState => {
console.log('multiState: ' + multiState);
// some logic
})
.catch(err => console.error(err))
;
calculateOrderAmount();
You cannot return values from an inner callback and reach the outer function, I would suggest
1- only use promises
2- wrap your code with promise to be sure that await will return the async result the way you expect to have.
getMultiStrats = async () => {
return new Promise((resolve, reject) => {
MultiStrats.findOne({}, (err, multiStrats) => {
if (err) {
return err
}
if (!multiStrats) {
console.log('MultiStrat not found')
reject('MultiStrat not found')
}
console.log('returns MultiStrat: ' + multiStrats)
resolve(multiStrats);
})
.catch(err => {
console.log(err);
reject(err)
})
})
}
async function calculateOrderAmount(balance, ticker) {
try {
const multiState = await StrategyController.getMultiStrats()
console.log('multiState: ' + multiState)
// some logic
} catch (error) {
console.error(error);
}
}
Assuming that you use mongoose. I suggest using the promise interface like described in the documentation.
const getMultiStrats = async () => {
const query = MultiStrats.findOne({});
let multiStrats;
try {
multiStrats = await query.exec();
} catch (error) {
return error;
}
if (multiStrats) {
console.log("returns MultiStrat: " + multiStrats);
} else {
console.log("MultiStrat not found");
}
return multiStrats;
}
I would personally not return the error, but instead just let the error be thrown. With the above code the caller of getMultiStrats has to figure out if there return value is the expected result or an error. If you don't catch the error, it is thrown further up to the caller.
const getMultiStrats = async () => {
const multiStrats = await MultiStrats.findOne({}).exec();
if (multiStrats) {
console.log("returns MultiStrat: " + multiStrats);
} else {
console.log("MultiStrat not found");
}
return multiStrats;
}
You can further simplify this if you where to leave the console.log of of the equation.
const getMultiStrats = () => MultiStrats.findOne({}).exec();

ExpressJs wait till MongoDB fetch data and loop through before the output

I'm having trouble in figuring this to work, I have one table from MongoDB (collection) for comments and another collection for Users.
When the page load it looks up the comment collection and selects the relevant comments, and then it searches the user table to find the name of the user who made the comment, the data will be combined and then the response is sent.
However, the output is sent before the data is fetched from the user table and added. How can I fix this, here is my code
var output = []
const Comments = require('comments.js')
const Users = require('users.js')
function delay( ) {
return new Promise(resolve => setTimeout(resolve, 300))
}
async function delayedProcess(item) {
await delay()
Users.findById(item.user, async function(err, result) {
Object.assign(item, {name: result.name})
output.push(item)
})
}
async function processArray(array) {
const promises = array.map(delayedProcess)
await Promise.all(promises)
return res.json({data: output})
}
Comments.find({page_id: id}).sort({post_date:-1}).limit(6).then(function(data) {
processArray(data)
})
You are not returning promise from the delayedProcess function.
There you go :-
const Comments = require('comments.js')
const Users = require('users.js')
const output = []
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
output.push({ ...item, name: result.name })
return resolve()
})
})
}
async function processArray(array) {
const promises = array.map(async(item) => {
await delayedProcess(item)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
However you will always get the concatenated array. So instead taking it globally, take it as local variable
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
return resolve({ ...item, name: result.name })
})
})
}
async function processArray(array) {
const output = []
const promises = array.map(async(item) => {
const it = await delayedProcess(item)
output.push(it)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
More simplified :- Since mongodb queries itself returns promise you do not need to use new Promise syntax.
async function processArray() {
const array = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
const output = []
const promises = array.map(async(item) => {
const it = await Users.findById(item.user).lean()
item.name = it.name
output.push(item)
})
await Promise.all(promises)
return res.json({ data: output })
}

Firestore query for loop with multiple values

I am attempting to retrieve a number of Firestore documents using data held in a string. The idea is that for each value in the array, i'd use Firestore query to retrieve the document matching that query and push it to another array. I am having a few issues achieving this. So far i've tried:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let queryRef = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
response.send(JSON.stringify(outputList));
});
I'm not entirely sure but i think one of the issues is that the for loop is being completed before the queries have a chance to finish.
P.s - this is being ran through Cloud Functions using Admin SDK.
Your queryRef is not actually a reference. It's a promise that resolves after your get/then/catch have finished. You need to use these promises to determine when they're all complete. The array will be populated only after they are all complete, and only then is it safe to send the response using that array.
Collect all the promises into an array, and use Promise.all() to get a new promise that resolves after they're all complete:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let promise = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
promises.push(promise);
}
Promise.all(promises).then(() => {
response.send(JSON.stringify(outputList));
}
.catch(err => {
response.status(500);
})
});
You might want to use these tutorials to better understand how to deal with promises in Cloud Functions:
https://firebase.google.com/docs/functions/video-series/
You need to look into promises, Never called a promise in a loop like this. First, you need to chunk your code which returns result from the DB(asynchronously) and uses Promise.all() to handle multiple promises.
utils.getData = async (item) => {
try {
const result = await db.collection("items").where('listedItems', 'array-contains', item).get();
return result;
} catch (err) {
throw err;
}
};
utils.getDataFromDB = async () => {
try {
const list = ["item1", "item2", "item3", "item4"];
const outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
const element = list[i];
promises.push(utils.getData(elem));
}
const result = await Promise.all(promises);
result.forEach((r) => {
if (r.empty) {
console.log('No matching documents.');
} else {
snapshot.forEach(doc => {
outputList.push(doc.data());
});
}
});
return outputList;
} catch (err) {
throw err;
}
}
module.exports = utils;
Here is my attempt at fully idiomatic solution. It needs no intermediate variables (no race conditions possible) and it separates concerns nicely.
function data_for_snapshot( snapshot ) {
if ( snapshot && !snapshot.empty )
return snapshot.map( doc => doc.data() );
return [];
}
function query_data( search ) {
return new Promise( (resolve, reject) => {
db
.collection("items")
.where('listedItems', 'array-contains', search)
.get()
.then( snapshot => resolve(snapshot) )
.catch( resolve( [] ) );
});
}
function get_data( items )
{
return new Promise( (resolve) => {
Promise
.all( items.map( item => query_data(item) ) )
.then( (snapshots) => {
resolve( snapshots.flatMap(
snapshot => data_for_snapshot(snapshot)
));
});
});
}
get_data( ["item1", "item2", "item3", "item4"] ).then( function(data) {
console.log( JSON.stringify(data) );
});
I used a simple mockup for my testing, as i don't have access to that particular database. But it should work.
function query_data( search ) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve([{
data: function() { return search.toUpperCase() },
empty: false
}])
});
});
}

Print result from promise map

Hi guys I have 2 methods.
checkVenueAvailability(venues) {
var replaced = venues.replace(/\t/g, "");
var venues = replaced.split(',');
var length = venues.length;
Promise.all(venues.map(venue => {
return new Promise((resolve, reject) => {
pool.query("SELECT * FROM peminjaman_venue WHERE nama_venue = ?", venue,
function (err, rows, fields) {
if (err) {
return reject(err);
}
return resolve(rows);
})
})
}))
}
And
mengajukan_event(req, res) {
helper.checkVenueAvailability(req.body.venue_1)
.then(function (result) {
console.log(result);
}).catch(function (err) {
console.log(err);
})
}
I want to print the result of checkVenueAvailability in mengajukan_event. How to achieve that. My code above just return error. Thankyou.
You aren't returning anything. Just return the promise from Promise.all:
return Promise.all(venues.map(venue => {
// ^^^^^^
Also note that there are DB adapters with a Promise-based API available now. Or, if you can't use one, you can use util.promisify.
const promiseQuery = util.promisify(pool.query);
Then
checkVenueAvailability(venues) {
var replaced = venues.replace(/\t/g, "");
var venues = replaced.split(',');
return Promise.all(venues.map(venue => promiseQuery("SELECT * FROM peminjaman_venue WHERE nama_venue = ?", venue));
}
(I also removed var length = venues.length; since length wasn't used for anything.)

Categories