Async function never returns - javascript

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.

Related

How to properly handle reject in Promises

We have this function in our code that is used to log in a user
const userLogin = loginData => {
return new Promise(async (resolve, reject) => {
try {
const res = await auth.post("/login", loginData);
resolve(res);
} catch (error) {
reject(error);
}
});
};
// Calling function
const loginSubmit = async values => {
try {
const res = await userLogin(values);
console.info(res);
} catch (error) {
console.error("Catch: ", error);
}
};
But from this stackoverflow answer, try-catch blocks are redundant in Promises. I wanted to try and clean this code, so I changed the code above into:
const userLogin = loginData => {
return new Promise(async (resolve, reject) => {
const res = await auth.post("/login", loginData);
if (res.status !== 201) {
reject(new Error("Error"));
}
resolve(res);
});
};
However, when I tried to login with incorrect credentials, the console logs an Uncaught (in promise) Error: Request failed with status code 400
I'm not really familiar with creating my own promises, so I don't know how to do this properly.
Couple of problems in your code:
You are unnecessarily creating a promise; auth.post(..) already returns a promise, so you don't need to create a promise yourself and wrap auth.post(...) inside a promise constructor.
Another problem in your code is that executor function (function passed to the promise constructor) is marked as async; it should not be an async function.
Your function could be re-written as:
const userLogin = async (loginData) => {
const res = await auth.post("/login", loginData);
if (res.status !== 201) {
throw new Error("Error"));
}
return res;
};
You could also re-write your function as:
const userLogin = async (loginData) => {
return auth.post("/login", loginData);
};
Don't forget to use the catch in the code that calls this function.
You might want to read the following article to understand whether you need the try-catch block: await vs return vs return await
I think that in your case since you call to async function inside the constructor of the promise you need to use try catch.
The answer you referred is correct as long as the error happened while you are in the constructor (i.e. the Promise object is in the making), however in your case the rejection of the auth function happens long after the Promise was constructed and therefore it is not rejecting it.
BTW - You don't have to await in the promise. You may do the following:
const userLogin = loginData => {
return new Promise(async (resolve, reject) => {
const prom = auth.post("/login", loginData)
.then((res) => {
if (res.status !== 201) {
reject(new Error("Error"));
}
resolve(res);
});
resolve(prom);
});
};
Since you resolve the async auth call, any rejection by the auth call will be reflect as a rejection from you function

Multiple awaits in an async function does not return [duplicate]

This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 2 years ago.
I have the following code on a server:
let tmpFileName;
// GET
app.get('/clicked', async (req, res) => {
let nullOutput = writeTmpFile("hello, world!");
await deleteTmpFile();
console.log("Hurray, finished!");
res.send({result:nullOutput});
})
function writeTmpFile(content){
tmpFileName = "tmp" + Math.random().toString() + "tsl";
return new Promise(resolve => {
fs.writeFile(tmpFileName, content, function (err) {
if (err) throw err;
console.log('Temp file creation successful.');
});
})
}
function deleteTmpFile(spec){
return new Promise(resolve => {
fs.unlink(tmpFileName, function (err) {
if (err) throw err;
console.log('Temp file deletion successful.');
});
})
}
However, in my console output, I only get
Temp file creation successful.
Temp file deletion successful.
However, if I delete await deleteTempFile(), then Hurray, finished! shows up on the console.
And more generally, how do I debug these patterns of problems?
Why is this happening?
I have rewritten your code, to showcase how to use promises.
Promise callback gets two functions as arguments: resolve and reject.
You should call resolve when operation finishes with success, and reject when it fails.
// I moved `tmpFileName` variable from here into the request handler,
// because it was "global" and would be shared between requests.
app.get('/clicked', async (req, res) => {
let tmpFileName = "tmp" + Math.random().toString() + "tsl"
let writingResult = await writeTmpFile(tmpFileName, "hello, world!")
let deletionResult = await deleteTmpFile(tmpFileName)
res.send({ writingResult, deletionResult })
console.log("Hurray, finished!")
})
function writeTmpFile (filename, content) {
return new Promise((resolve, reject) => {
fs.writeFile(filename, content, function (err) {
// on error reject promise with value of your choice
if (err) reject(err)
// on success resolve promise with value of your choice
resolve('Temp file creation successful.')
})
})
}
function deleteTmpFile (filename) {
return new Promise((resolve, reject) => {
fs.unlink(filename, function (err) {
if (err) reject(err)
resolve('Temp file deletion successful.')
})
})
}
For working with the file you can use writeFileSync instead writeFile. (Reference).
For multiple Promise you can use the Promise.all method.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
from MDN

Changing script from request to axios - log pending promise

I'd like some help please as I'm quite new in node.js and working with node packages.
I'm having the following script which makes a GET http request running on node using request which is deprecated now
const foo = (bar, callback) => {
const url = 'https://some.api.com?key=abc123';
request({url: url, json: true}, (error, response) => {
if (error) {
callback('Oops, there is an error!', undefined);
} else if(response.body.foobarArray.length === 0) {
callback('No data found', undefined);
} else {
callback(undefined, {
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
})
}
});
}
console.log(foo('Hello')); // this logs {foobar1: 'Hello', foobar2: 'World'}
I'm trying to rewrite it using axios instead, so this is my code
const foo = async (bar) => {
const url = 'https://some.api.com?key=abc123';
try {
const response = await axios.get(url);
if (response.body.foobarArray.length === 0) {
return 'No data found';
} else {
return {
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
};
}
} catch (error) {
return 'Ooops! Something went wrong :(';
}
};
console.log(foo('Hello')); // This logs `Promise { <pending> }`
I'm not sure what I'm doing wrong here as I'm not very familiar how promises work exactly, but how can I fix this?
const foo = async (bar) => {
const url = 'https://some.api.com?key=abc123';
try {
return await axios.get(url).then(response => {
return new Promise((resolve, reject) => {
if (response.body.foobarArray.length === 0) {
return reject('No data found');
} else {
return resolve({
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
});
}
})
}).catch(err => {
return Promise.reject(err);
});
} catch (error) {
// return 'Ooops! Something went wrong :(';
return Promise.reject(`an error occurred : ${error}`);
}
};
foo('hello').then(result => {
console.log(result);
}).catch(err => {
console.log(`error ! : ${err}`);
});
async functions returns a promise. async functions use an implicit Promise to return its result. Even if you don't return a promise explicitly async function makes sure that your code is passed through a promise
as you are using axios asynchronous , it's response is a promise which must be handled inside .then().catch() functions .
if no error occurs you can access the response inside your .then() , else you will have access to your error on .catch()
inside your .then() you can now do what you want with data , returning a new Promise , using resolve() for success and reject() for failure .
You have 2 options here:
Option 1
Any async function returns a Promise (behind the scenes) so:
foo('Hello').then(console.log).error(console.error);
Option 2
You need to await for the result of foo function but, at the moment, you can't use await out of function scope level. So:
async function main() {
try {
const result = await foo('Hello');
console.log(result);
} catch (err) {
console.error(err);
}
}
main();
In future Node.js releases, using await at global scope will be allowed.

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

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

UnhandledPromiseRejectionWarning in protractor

I'm trying to retrieve values from mongoDB and its giving me the
UnhandledPromiseRejectionWarning: MongoError: topology was destroyed
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().
[DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Following is the scaled down code version
CLASS 1
connectToMongoDatabase() {
try {
return new Promise((resolve, reject) => {
mongoclient.connect('mongodb://************************', (err, db) => {
if (err) {
reject(err);
}
else {
resolve(db);
}
});
});
}
catch (err) {
console.log(err);
}
}
fetchIssuesFromMongo(dbName, collectionName, query, db) {
try {
let dbo = db.db(dbName);
return new Promise((resolve, reject) => {
let collection = dbo.collection(collectionName);
collection.find(query, (err, result) => {
if (err) {
reject(err);
}
else {
resolve(result);
dbo.close();
}
});
});
}
catch (err) {
console.log(err);
}
}
CLASS 2
executeQuery(issueCount){
this.CLASS1.connectToMongoDatabase().then((db) => {
this.CLASS1.fetchIssuesFromMongo(dbName, collectionName, query, db).then((result: any) => {
expect(result.count()).toEqual(issueCount);
});
});
}
SPEC FILE
it('verify result', (done) => {
CLASS2.executeQuery(6).then(() => {
done();
});
});
What I think is the test fails after this.CLASS1.connectToMongoDatabase().
Is there any issue with how I'm using promises ? I'm resolving all the promises and have reject statements also in place.
Any suggestions ?
Updating your Class 1
Remove the try catch since it will never catch on a returned promise. Here's the change for fetchIssuesFromMongo. You should do something similar for connectToMongoDatabase
fetchIssuesFromMongo(dbName, collectionName, query, db) {
const dbo = db.db(dbName);
return new Promise((resolve, reject) => {
const collection = dbo.collection(collectionName);
collection.find(query, (err, result) => {
if (err) {
reject(err); // at this point you should call a .catch
} else {
dbo.close(); // switching the order so the close actually happens.
// if you want this to close at the exit, you should
// probably not do it like this.
resolve(result);
}
});
});
}
Fixing the executeQuery in Class 2
In your executQuery:
executeQuery(issueCount){
// if connectToMongoDatabase is thenable, then you should also call .catch
// you should also return a promise here so your Protractor code can actually
// call .then in `CLASS2.executeQuery(6).then`
return this.CLASS1.connectToMongoDatabase().then((db) => {
this.CLASS1.fetchIssuesFromMongo(dbName, collectionName, query, db).then((result: any) => {
expect(result.count()).toEqual(issueCount);
}).catch(e => {
console.log(e);
});
}).catch(e => {
console.log(e);
});
}
Think about using async / await.
This usually helps clear up the nested chain of promises. I prefer this.
// this returns implicitly returns a Promise<void>
async executeQuery(issueCount) {
// valid to use try catch
try {
const db = await this.CLASS1.connectToMongoDatabase();
const result = await this.CLASS1.fetchIssuesFromMongo(dbName, collectionName, query, db);
expect(result.count()).toEqual(issueCount);
} catch(e) {
console.log(e);
}
}
Use async / await in your Protractor test
Finally in your Protractor test you should turn off the selenium promise manager. This is something you'll do in your configuration file. SELENIUM_PROMISE_MANAGER: false,
Next you can use async / wait in your test.
it('verify result', async () => {
await CLASS2.executeQuery(6);
});
I'm not a fan of expecting a condition in your class and it might be better to return the value from class 2. So I would maybe return a Promise from executeQuery.
const issueCount = 6;
const queryResult = await CLASS2.executeQuery(issueCount);
expect(queryResult).toEqual(issueCount);
Hope that helps.

Categories