Having issues with using async and await. I'm executing two queries and then saving the result to a temp variable. After I have collected the response from all executed queries, I'm going to send that to the client.
Here is my current example code.
module.exports = (app) => {
app.get('/api/stats', (req, res) => {
let fetch1 = '';
let fetch2 = '';
conn.query('query here', [], async (error, results) => {
if (error) {
return res.send({
success: false,
message: 'There was an error.'
});
} else {
fetch1 = results;
}
});
conn.query('query here', [], async (error, results) => {
if (error) {
return res.send({
success: false,
message: 'There was an error.'
});
} else {
fetch2 = results;
}
});
// I need to wait until the queries have resolved so that I can send the correct data
return res.send({
success: true,
fetch1: fetch1,
fetch2: fetch2
});
});
};
I basically need to wait until the queries have been resolved so that I can send the correct data towards the end.
Can anyone explain how I can use await and async to accomplish this?
Thanks.
You can only await a Promise, so for functions that don't return Promises you need to create a Promise wrapper. This needs to be done per call that would previously use a callback, but you can make a helper function per function you need to wrap.
function queryPromise(query, parameters) {
return new Promise((resolve, reject) => {
conn.query(query, parameters, (err, results) => {
if(err) {
reject(err);
} else {
resolve(results);
}
});
});
}
module.exports = (app) => {
app.get('/api/stats', async (req, res) => {
try {
let fetch1 = await queryPromise('query here', []);
let fetch2 = await queryPromise('query here', []);
res.send({
success: true,
fetch1: fetch1,
fetch2: fetch2
});
} catch {
res.send({
success: false,
message: 'There was an error.'
});
}
});
};
From my knowledge, I usually apply async to functions and perform await on certain variables (inside the function) that need to be acquired from a specific database.
So to implement this into your function containing the async tag, you could possibly do:
fetch1 = await results;
fetch2 = await results;
This will wait until the data is attached onto the variable fetch1 and fetch2 before continuing on with the code.
Sorry if this was very vague, hopefully this was somewhat helpful.
Related
I am using the package "fcm-node" in order to send notifications to certain device id.
the sendNotification function is as follows:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
function sendNotification(registrationToken, title, body, type, key) {
const message = {
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
};
fcm.send(message, function (err, value) {
if (err) {
console.log(err);
return false;
} else {
console.log(value);
return value;
}
});
};
module.exports = {
sendNotification
};
The api function I use to call this function is as follows:
router.get('/test', async (req, res, next) => {
const promise = new Promise((resolve, reject) => {
let data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
console.log(data)
if (data == false) reject(data);
else resolve(data);
});
promise
.then((data) => { return res.status(200).send(data); })
.catch((data) => { return res.status(500).send(data) })
});
When I console.log the "err" and "value" from the sendNotification, I get either of the followings:
{"multicast_id":4488027446433525506,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1652082785265643%557c6f39557c6f39"}]};
{"multicast_id":8241007545302148303,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
In case it is successful, I made sure that the device is receiving the notification.
The problem is in the api's data. It is always "undefined" and weither send notification is successful or not I get the 200 Ok status.
What seems to be the problem?
You can't return anything from the function (err, value) {} callback of a node-style asynchrnous function.
Your sendNotification() function needs to return a promise. util.promisify() makes the conversion from a node-style asynchronous function to a promise-returning asynchronous function convenient. Note the return, it's important:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
const { promisify } = require('util');
fcm.sendAsync = promisify(fcm.send);
function sendNotification(registrationToken, title, body, type, key) {
return fcm.sendAsync({
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
});
}
module.exports = {
sendNotification
};
Now you can do what you had in mind
router.get('/test', async (req, res, next) => {
try {
const data = await sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
return res.status(200).send(data);
} catch (err) {
return res.status(500).send(err);
}
});
Maybe it will help, at first try to return your response (the promise) in sendNotification, as actually you have a void function, that's why it's always undefined and after in your route
router.get('/test', async (req, res, next) => {
try {
const data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
if (data) {
return res.status(200).send(data);
}
} catch(err) {
return res.status(500).send(err);
}
});
I am using Nodejs and Mongoose. When I render the page nothing happens, the page just attempts to load. When I write to the console i can view all the entries in the collection (Kp). Please assist, as to what is wrong in my code.
router.get('/list', (req, res) => {
const arrayT = [];
Kp.find({})
.then(element => {
arrayT.forEach(function (element) {
arrayT.push({
summary: element.summary,
detailDesc: element.detailDesc
});
res.render('list', { arrayT });
})
})
.catch(err => console.log(err));
});
Your arrayT.forEach isn't iterating over anything since arrayT is empty and because it doesn't iterate over anything the res.render() line of code isn't ever hit. You could prove this with console.log();
My advice to you is to utilize async await and avoid promises as they can potentially lead to nested code like callbacks.
Preferred code:
router.get('/list', async (req, res) => {
try {
const arrayT = [];
const listOfKPs = await Kp.find({})
listOfKPs.forEach( ele => {
arrayT.push({
summary: element.summary,
detailDesc: element.detailDesc
});
});
return res.render('list', { arrayT });
}
catch(err) {
console.log(err);
return res.render('list', { [] }); // render empty list
}
});
Solution to my problem
router.get('/list', async (req, res) => {
try {
const list = await Kp.find({}).select('summary detailDesc -_id');
list.forEach(function(element){});
res.render('list', {list});
}
catch (err) {
res.json({ message: 'Test is not working ' + err });
console.log(err);
}
});
Thank you all for assisting with comments and answers.
I'm a newbie in node js Development. I just learn node js in short time ago. Here I create a router file
import express from 'express';
import storyController from '../../controllers/story';
const router = express.Router();
router.post('/', (req, res) => {
const { author, title } = req.body;
console.log(author);
const story = {
author: req.body.author,
title: req.body.title,
content: req.body.content,
tags: req.body.tags
};
storyController.createStory(story, function(error, result){
console.log("halo");
if(error)
res.status(500).send({ success: false, message: error.message});
res.status(200).send({ success: true, message: "Success"});
});
});
Then, i create one more file referred as the controller here
import mongoose from 'mongoose';
const Story = mongoose.model('Story');
exports.createStory = async (story) => {
const { author, title } = story;
if(!author){
console.log("hahaAuthor");
return {
error: true,
message: 'You must write an author name!'
};
}
if(!title) {
console.log("haha");
return {
error: true,
message: 'You must write a title!'
}
}
const newStory = new Story({
author: author,
title: title,
content: story.content,
tags: story.tags,
slug: ''
});
newStory.save().then((story) => {
return { error: false, result: story};
}).catch((error) => {
return { error: error};
})
};
But, unfortunately I don't know why my function in router file doesn't call the callback function. The console.log doesn't even called yet. Please help. Otherwise, maybe you have a better way to do this. Thanks!
As createStory is an async function. Change your code like this. You are mixing async with Promise and callback
exports.createStory = async (story) => {
...
// Change the promise to await
let story = await newStory.save();
return { error: false, result: story};
};
Error should be handled in the controller with Promise catch clause.
Something like
router.post('/', (req, res) => {
storyController.createStory.then(data => {
return res.json({error: false, data: data});
}).catch(e => {
return res.json({error: true});
})
});
Note: Either use callback or async. async is the best option now adays
May be this can work:
// 1. callback style
newStory.save().then((story) => {
return cb(null, story);
}).catch((error) => {
return cb(error);
})
// 2. await
await newStory.save();
// controller
router.post('/', (req, res) => {
storyController.createStory.then(data => {
return res.json(...);
}).catch(e => {
return res.json(...);
});
If you use callback style, Error-First Callback is better.
I want to fetch icon PNGS from gridfs out of our mongodb database with mongoose. These icons then should be zipped and served at a specific route.
My current code is as follows:
var zip = require("node-native-zip");
async function getZipFile() {
//get the events out of the DB
db.Category.find({}).populate('icons.file').exec(async function (err, cats) {
if (err) {
//oh oh something went wrong, better pass the error along
return ({
"success": "false",
message: err
});
}
else {
//all good, build the message and return
try {
const result = await buildZip(cats);
return ({
"success": "true",
message: result
});
}
catch (err) {
console.log("ZIP Build Failed")
}
}
});
}
async function buildZip(cats) {
let archive = new zip();
for (let i = 0; i < cats.length; i++) {
cats[i].icons.forEach(function (icon) {
if (icon.size === "3x") {
db.Attachment.readById(icon.file._id, function (err, buffer) {
if (err)
return;
archive.add(cats[i]._id + ".png", buffer);
});
}
});
//return when everything is done
if (i === cats.length - 1) {
return archive.toBuffer();
}
}
}
module.exports =
{
run: getZipFile
};
I don't want to build the zip before runtime, as I want to rename the icons acording to the category ID. I tried going for a async/await structure, but my callback is being returned before the building of the zip file even started.
I'm calling the function with
case 'categoryZip':
categoryHelper.getZipFile.run().then((result) => {
callback(result);
});
break;
This should (as far as I understood it) fire the callback when the zipping is done, but I think I'm missing something essential here.
I wrapped both your callback methods into promises, and also awaited your double for-loop of callbacks in parallel using Promise.all() since they don't rely on each other and I assume they don't need to be in any particular order in the zip file:
async function getZipFile() {
//get the events out of the DB
return new Promise((resolve, reject) => {
db.Category.find({}).populate('icons.file').exec(async function(err, cats) {
if (err) {
//oh oh something went wrong, better pass the error along
reject({
success: false,
message: err
});
} else {
//all good, build the message and return
try {
const result = await buildZip(cats);
resolve({
success: true,
message: result
});
} catch (err) {
console.log("ZIP Build Failed")
reject({
success: false,
message: err
});
}
}
});
});
}
async function buildZip(cats) {
let archive = new zip();
await Promise.all(
cats.map(cat => Promise.all(cat.icons
.filter(icon => icon.size === '3x')
.map(icon => new Promise((resolve, reject) => {
db.Attachment.readById(icon.file._id, function(err, buffer) {
if (err) return reject(err);
archive.add(cat._id + ".png", buffer);
resolve();
});
}))
))
);
return archive.toBuffer()
}
How would I structure a function that has multiple Mongoose.findOne() nested in each other?
I need to do something like
const userId = '...';
const postId = '...';
const imageId = '...';
User.findById(userId).then(user => {
if (!user) {
return res.status(400).json({
status: 'error',
err: 'User not found',
});
}
Post.findById(postId).then(post => {
if (!post) {
return res.status(400).json({
status: 'error',
err: 'Post not found',
});
}
Image.findById(imageId).then(image => {
if (!image) {
return res.status(400).json({
status: 'error',
err: 'Image not found',
});
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
}).catch(err => { .. });
}).catch(err => { .. });
}).catch(err => { .. });
Since Collection.findById() returns a promise, I guess I should use chaining instead of this structure.
So it might be something like
User
.findById(userId)
.then(user => Post.findById(postId))
.then(post => Image.findById(imageId))
.then(image => {
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
});
.catch(err => { .. });
but I don't know how to access the variables user, post, and image, and how to throw the errors, so I can access them in my catch statement.
Edit
I have tried this
async function getPostAsync() {
const userId = '597989c668189f31483ffdbf';
const postId = '597989c62624ea74750c74f8';
if (!userId) {
throw new Error('User id missing');
}
if (!postId) {
throw new Error('Post id missing');
}
const user = await User.findById(userId);
const post = await Post.findById(postId);
return post;
}
app.get('/', (req, res) => {
getPostAsync().then(post => {
res.json({
status: 'success',
});
}).catch(err => {
res.status(400).json({
status: 'error',
err
});
})
});
but I just receive
{
"status": "error",
"err": {}
}
Am I doing something wrong?
But I get the same result even with
async function getPostAsync() {
throw new Error('msg');
return Post.find();
}
so I might be calling the async function wrong.
You can use Promise.all:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(result)=>{
let user = result[0];
let post = result[1];
let image = result[2];
})
.catch(err => { .. });
Or with destructing assignment:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(([user, post, image])=>{...})
.catch(err => { .. });
You can't access those variables inside a later promise's then, but you can get round it by assigning the local resolved values to global variables
let globalUser, globalPost; // create variables for later
User
.findById(userId)
.then(user => {
globalUser = user; // assign to global
return Post.findById(postId)
})
.then(post => {
globalPost = post; // assign to global
return Image.findById(imageId)
})
.then(image => {
// DO SOMETHING WITH VARIABLES 'globalUser', 'globalPost', AND 'image'
})
.catch(err => {... });
EDIT: or when using async/await:
async function() {
const user = await User.findById(userId);
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
// do something with user, post and image
}
Seeing as your promises don't rely on each other you could also use Promise.all() in an async function:
async function() {
const result = await Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
]);
const [user, post, image] = result;
// do something with user, post and image
}
EDIT 2: Error handling
async function getImage() {
let user;
try {
user = await User.findById(userId);
} catch (error) { // deal with rejection of `User.findById`
// do something with error
}
// if these fail the entire function will throw
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
return image;
}
getImage()
.then(image => {... })
.catch(error => {... }); // deal with rejection of `getImage` as a whole
The above code showcases the ways you can handle errors in an async function. The first is how we deal with an error in the User.findById function, by simply wrapping it in a try catch block.
The second method is by simply letting the entire async function throw an error. I.e. if the Post.findById or Image.findById promises reject, the entire getImage() promise will reject, which you can deal with in the .catch() handler.