Async Await fails in NODE.JS when client connects to server - javascript

When my React client connects to the server :
index.js :
var mongoAPI = require("./MongoDB/mongodb.js");
app.get("/getImages" , async function(req,res) {
var db_images = await mongoAPI.getAllImages(); // this is undefined
console.log(db_images);
res.json(db_images);
});
It goes to the database :
mongodb.js :
getAllImages = async () => {
await MongoClient.connect(url, function(err, db) {
if (err) {
throw err;
}
var dbo = db.db("PicturesDB");
dbo
.collection("Images")
.find({})
.toArray(function(err, result) {
if (err) {
// res.send(err);
return err;
} else {
// res.send(JSON.stringify(result));
return JSON.stringify(result);
}
});
});
};
module.exports = {
...
...
getAllImages: getAllImages
};
However db_images is always undefined , even though that the data returns properly (I checked with "console.log' in the getAllImages method).
Where did I go wrong with the request ?

The getAllImages function doesn't return your value. It just returns what the connect method returns.
Since you are using await to call the getAllImages function, return a promise and resolve it with your value.
getAllImages = async () => {
return new Promise((resolve, reject) => {
MongoClient.connect(url, function(err, db) {
if (err) {
reject(err)
}
var dbo = db.db("PicturesDB");
dbo
.collection("Images")
.find({})
.toArray(function(err, result) {
if (err) {
reject(err)
} else {
resolve(JSON.stringify(result));
}
});
});
})
};
However, the most accurate pattern is to use the native promise interface of your library as pointed out by jfriend00 in a comment.

Your current code is attempting to use await, but you aren't using the promise interface in the database. With Mongodb, when you pass a regular callback to something like .connect() or .toArray(), it does not return a promise so then your await does nothing useful. But, if you don't pass that callback at all (and are using a newish version of the database library), then it returns a promise that you can use.
Here's what I would suggest:
getAllImages = async (url) => {
const db = await MongoClient.connect(url);
const dbo = db.db("PicturesDB");
const result = await dbo.collection("Images").find({}).toArray();
return JSON.stringify(result);
}
This uses the promise interface on .connect() and awaits that. If the connection fails, the promise will reject and await will throw which will cause the async function to reject the promise it already returned so the caller will properly see the error.
And, keep in mind that when we do return JSON.stringify(result); in an async function, that is telling the interpreter that we want JSON.stringify(result) to become the resolved value of the promise that the function has already returned (all async functions return a promise).
And, then you need proper error handling in your request handler in case something goes wrong with getAllImages():
app.get("/getImages" , async function(req,res) {
try {
const db_images = await mongoAPI.getAllImages();
console.log(db_images);
res.json(db_images);
} catch(e) {
// database error
console.log(e);
res.status(500).send("database error");
}
});

We should not mix aysnc await with callbacks, i have written the code only with async await, and mongoclient now returns promise.
getAllImages = async (url) => {
const db = await MongoClient.connect(url);
if (!db)
throw "An error occurred";
var dbo = db.db("PicturesDB");
const result = await dbo.collection("Images").find({}).toArray()
if (!result)
return "error occurred";
else
return JSON.stringify(result);
}

Related

Returning result of mongodb query, and exporting with node

I am attempting to export the result of a MongoDB collection using
exports.getAllQuestions = async function (){
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("Time4Trivia");
return dbo.collection("Questions").find({}).toArray().then();
});
};
I am then referencing this return and attempting to render it in a jade file
router.get('/play', function(req, res, next) {
var questionsArray = mongoDAL.getAllQuestions()
console.log("This is the questions array: " + questionsArray)
res.render('play', { title: 'Time 4 Trivia',user: req.session.user, questions: questionsArray});
});
However, I am only returning an [promise object]. For example my console log returns
This is the questions array: [object Promise]
How would I go about actually sending the array to my jade file from my original file?
You just got your syntax mixed up a little, I recommend you go read about Promises and how to use them properly, the easiest way with your current syntax is to just wrap it with a promise:
exports.getAllQuestions = async function (){
return new Promise((resolve, reject) => {
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("Time4Trivia");
return resolve(await dbo.collection("Questions").find({}).toArray())
});
})
};
Then you just need to wait for it:
var questionsArray = mongoDAL.getAllQuestions()
The code could be cleaned up using async/await syntax insted:
exports.getAllQuestions = async function (){
const db = await MongoClient.connect(url);
var dbo = db.db("Time4Trivia");
return await dbo.collection("Questions").find({}).toArray()
};

Return value from externally called fs.Readfile() in node

I am trying to get a value returned from a function where I read and write a file using fs.readFile/writeFile in Node.
In my main server.js file, a request comes in and I then want to send an email from another file called sendEmail.js:
const fs = require('fs')
const sendMail = require('./sendEmail')
async function sendAnEmail() {
let resultOfSend = await sendMail.sendEmail()
resultOfSend.then((result)=>{
// return the result
}
}
sendAnEmail();
In sendEmail I first read a file to get the email to send to,
then write to a second file
then, if all is good, I send an email (from a separate function):
async function sendEmail() {
// Check if user exists
fs.readFile('./file.json', (err, data) => {
if(err) {
throw error
}
else {
let users = JSON.parse(data)
let dataToWrite = JSON.stringify(users)
fs.writeFile('./file2.json', dataToWrite, (err) => {
if(err) {
console.error(err)
throw error
}
else {
return generateEmail(users)
.then((info) => {
return info
})
.catch(console.log('err'))
}
})
}
})
}
async function generateEmail(user) {
let msgText = 'hello world'
// Set the mail options
const mailOptions = {
...
}
// Send the mail
let info = await transporter.sendMail(mailOptions)
return info
}
module.exports = {sendEmail}
What I can't get is a value for the resultOfSend variable. Keeps coming back undefined, I think because the promise hasn't yet been fulfilled.
How do I get a value to return from the sendEmail function back to the server.js file where it's called from?
You're using await and async in sendEmail but not returning any Promise (So the sendEmail function doesn't return anything and this is why you get undefined).
Nevertheless, on the response you're trying to call .then() even though you used await.
So you should:
return Promise in the sendEmail function.
decide how you want to handle it, if you use async-await then dont use .then() and just analyze the variable and vice versa.
generateEmail() function should also return Promise.
For example:
async function sendEmail() {
return new Promise((resolve, reject) => {
// Check if user exists
fs.readFile('./file.json', (err, data) => {
if(err) {
reject()
}
else {
let users = JSON.parse(data)
let dataToWrite = JSON.stringify(users)
fs.writeFile('./file2.json', dataToWrite, (err) => {
if(err) {
console.error(err)
reject()
}
else {
generateEmail(users)
.then((info) => {
resolve(info)
})
.catch(
console.log('err')
reject()
)
}
})
}
})
})
}

How to correctly use collection in strict mode with async await

So I'm trying to figure out how to use the collection strict mode with async/await. It needs a callback, but I can't figure out how to use it properly since it doesn't seem to be running any command.
It gives the error:
UnhandledPromiseRejectionWarning: MongoError: server instance pool was destroyed
Here's what I have so far.
const cursor =
await
db
.collection(
"invalidator",
{
strict: true
},
async function (error, cursor)
{
console.log(chalk.yellow("running callback"))
const result = await
cursor
.insertOne({
// created: new Date(),
test: "test"
})
console.log(result)
}
)
db is just the MongoClient
const db = await MongoClient.connect(
url,
{
useNewUrlParser: true,
},
)
Just to be clear the collection() method does not return a Promise. It's not actually meant to return anything and the callback interface is only needed for "strict mode" which means MongoDB cannot "create a collection" which does not already exist by the given name when you subsequently try to do anything.
In the shortest demonstrator, you basically need to wrap the "whole thing" in a Promise manually if you intend to await the actions inside the callback:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const client = await MongoClient.connect(uri, opts);
let db = client.db('test');
// Expect to wait or fail if the collection named does not already exist
await new Promise((resolve, reject) => {
db.collection('test', { strict: true }, async (err, collection) => {
if (err) reject(err);
// really should have an "inner" try..catch to "bubble" any errors.
try {
// insert something
let result = await collection.insertOne({ a: 1 });
log(result);
resolve(); // resolve the Promise
} catch(e) {
reject(e);
}
});
})
log("I waited");
client.close();
} catch(e) {
console.error(e);
} finally {
process.exit()
}
})()
For completeness, you really should have a try..catch around any "inner" operations of the callback such as the insertOne() here and subsequently reject() on any error within the catch block.

node.js mongodb find() results to array but not use .filter

I trying to use Array.filter() on result returned from mongodb.find(),
but it's not working! I am getting this error:
(TS) Property 'filter' does not exist on type '{}'.
function find(collectionName: string, filter: any = {},callback: Function) {
const MongoClient = require('mongodb').MongoClient;
let url = "mongodb://localhost:27017/test";
MongoClient.connect(url, function (err, db) {
if (err) {
callback(err, null);
//throw err;
db.close();
} else {
let rs = db.collection(collectionName).find(filter, { _id: false });
rs.toArray(function (err, result) {
if (err) throw err;
callback(null, result);
db.close();
});
}
});//MongoClient
}
The main problem in your code, that you use asynchronous function, as a regular function, which returns a result. Async function returns a promise, or accept callback, which is calling when async operation is finished. Callbacks are produces callback hell, so it's much better use promises or async/await feature, added in ES7.
The fixed code, could looks so:
function find(collectionName: string, filter: any = {}) {
const MongoClient = require('mongodb').MongoClient;
let url = "mongodb://localhost:27017/test";
return MongoClient
.connect(url)
.then(db => db.collection(collectionName).find(filter, { _id: false }))
.then(rs => rs.toArray())
.finally(() => db.close());
}
One note, currently, find function doesn't accept callback parameter, it returns a promise, so to use result in calling code, you should create a promise chain:
find(...)
.then(results => // TODO: use results here)
.catch(err => // TODO: process error here);

Async function never returns

I'm using Node version 7.6.0 to try out the native async and await features.
I'm trying to figure out why my async call just hanging never actually resolves.
NLP module:
const rest = require('unirest')
const Redis = require('ioredis')
const redis = new Redis()
const Promise = require('bluebird')
const nlp = {}
nlp.queryCache = function(text) {
return new Promise(function(resolve, reject) {
redis.get(text, (err, result) => {
if (err) {
console.log("Error querying Redis: ", err)
reject(new Error("Error querying Redis: ", err))
} else {
if (result) {
let cache = JSON.parse(result)
console.log("Found cache in Redis: ", cache)
resolve(cache)
} else {
resolve(null)
}
}
})
})
}
nlp.queryService = function(text) {
console.log("Querying NLP Service...")
return new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => {
redis.set(text, JSON.stringify(text))
resolve(response.body)
})
})
}
nlp.query = async function(text) {
try {
console.log("LET'S TRY REDIS FIRST")
let cache = await nlp.queryCache(text)
if (cache) {
return cache
} else {
let result = await nlp.queryService(text)
console.log("Done Querying NLP service: ", result)
return result
}
} catch (e) {
console.log("Problem querying: ", e)
}
}
module.exports = nlp
The module consumer:
const modeMenu = require('../ui/service_mode')
const nlp = require('../nlp')
const sess = require('../session')
const onGreetings = async function(req, res, next) {
let state = sess.getState(req.from.id)
if (state === 'GREET') {
let log = {
middleware: "onGreetings"
}
console.log(log)
let result = await nlp.query(req.text)
console.log("XXXXXXXX: ", result)
res.send({reply_id: req.from.id, message: msg})
} else {
console.log("This query is not not normal text from user, calling next()")
next()
}
};
module.exports = onGreetings;
I'm unable to get the code to proceed to following line:
console.log("XXXXXXXX: ", result)
I can see that the query was successful in the NLP module
Edit: Added console.log statement to response body
The most likely cause is an error in a Promise that you aren't catching. I find it helps to avoid try-catch in all but the top calling method, and if a method can be await-ed it almost always should be.
In your case I think the problem is here:
nlp.queryService = function(text) {
console.log("Querying NLP Service...")
return new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => {
redis.set(text, JSON.stringify(text)) // this line is fire and forget
resolve(response.body)
})
})
}
Specifically this line: redis.set(text, JSON.stringify(text)) - that line is calling a function and nothing is catching any error.
The fix is to wrap all your Redis methods in promises, and then always await them:
nlp.setCache = function(key, value) {
return new Promise(function(resolve, reject) {
redis.set(key, value, (err, result) => {
if (err) {
reject(new Error("Error saving to Redis: ", err));
} else {
resolve(result);
}
});
})
}
nlp.queryService = async function(text) {
console.log("Querying NLP Service...")
const p = new Promise(function(resolve, reject) {
rest.get('http://localhost:9119?q=' + text)
.end((response) => { resolve(response.body) });
// This is missing error handling - it should reject(new Error...
// for any connection errors or any non-20x response status
});
const result = await p;
// Now any issue saving to Redis will be passed to any try-catch
await nlp.setCache(text, result);
return;
}
As a general rule I find it's best practise to:
Keep explicit promises low level - have Promise wrapper functions for your rest and redis callbacks.
Make sure that your promises reject with new Error when something goes wrong. If a Promise doesn't resolve and doesn't reject then your code stops there.
Every call to one of these promise wrappers should have await
try-catch right at the top - as long as every Promise is await-ed any error thrown by any of them will end up in the top level catch
Most issues will either be:
You have a Promise that can fail to resolve or reject.
You call an async function or Promise without await.

Categories