I have an Express app and some function in server.js code is like this:
server.post('/post', (req, res) => {
//some code here...
function a() {
return new Promise(resolve => {
//some code here...
resolve(`result`)
});
};
async function output() {
console.log('Waiting');
const result = await a();
console.log(result);
//some code here...
};
output();
});
It works good but too nested to read. I want to move the function a() outside the server.post like:
function a() {
return new Promise(resolve => {
//some code here...
resolve(`result`)
});
}
server.post('/post', (req, res) => {
//some code here...
a();
async function output() {
console.log('Waiting');
const result = await a();
console.log(result);
//some code here...
};
output();
});
But like this cannot work as before...
In this case how to reduce the complexity of the first example?
You can usually handle it with this pattern:
server.post('/post', async (req, res, next) => {
// Some async code here
let stuff = await example();
await a(stuff);
res.send(...);
next();
});
The key here is to have a next argument so you can chain through when the promises wrap up. This is a callback function that must be called. Failing to call it leaves your request hanging.
Related
I have an express backend application. The problem I have is that all the routes contains the same try-catch piece which causes code bloat in my program:
// routes.js
router.get('/', async (req, res, next) => {
try {
const data = extractData(req)
await foo(data)
} catch (err) {
next(err)
}
})
// controllers.js
async function foo(data) {...do smh}
As you see above, try { extractData() } catch (err) { next(err) } portion of the code exists in all of the routes defined in the app.
I tried to create a wrapper function that takes controller function as parameter and use it as:
// routes.js
router.get('/', controllerWrapper(req, res, next, foo))
// controller-wrapper.js
async function controllerWrapper(req, res, next, controllerFunc) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
But this does not work due to function being invoked, and not being actually a callback.
How can I achieve this?
You should use a closure for this, so you can return the middleware function from controllerWrapper and use the controllerFunc inside the returned middleware
function controllerWrapper(controllerFunc) {
return async function (req, res, next) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
}
router.get('/', controllerWrapper(foo))
I would like to use await method inside the async.Series
Here is my node js code
const async = require("async");
async.eachSeries(myArr, function (arr, callback) {
let test = await db.collection('coll').findOne({ _id: arr }); //I couldn't use await here
callback(null, test);
}, function (err) {
console.log("Done");
});
I have tried
async.eachSeries(myArr, async function (arr, callback) {
let test = await db.collection('coll').findOne({ _id: arr }); //It not working here
callback(null, test);
}, function (err) {
console.log("Done");
});
And
async.eachSeries(myArr, async.asyncify(async(function (arr, callback) {
let test = await db.collection('coll').findOne({ _id: arr }); //It not working here
callback(null, test);
})), function (err) {
console.log("Done");
});
Correct me If the approach is wrong or let me know how to achieve await inside the async each.
As async document said:
Using ES2017 async functions Async accepts async functions wherever we
accept a Node-style callback function. However, we do not pass them a
callback, and instead use the return value and handle any promise
rejections or errors thrown.
If you still pass the callback function as the second parameter of eachSeries 's callback function, it will not work as expected. Just remove
callback and return the result:
async.eachSeries(myArr, async (arr) => { // remove callback parameter
const test = await db.collection('coll').findOne({ _id: arr }); // wait the response
// ...
return test; // return response
}, function (err) {
console.log("Done");
});
Or just use for...loop instead of eachSeries:
// inside a async function
for (const i of myArr) {
const test = await db.collection('coll').findOne({ _id: arr });
// Do something with `test`
}
console.log("Done");
Why mix callbacks with promises?
Right aproach will be use an async iterator Symbol.
If not first attempt would be valid
eachSeries(array, async function(arr, cb) {
await ....
});
I was reading production best practices given in express documentation, where they mentioned do not use synchronous functions. Now, earlier I used to write my controllers like this:
without async await, only call backs.
exports.getSubjectById = (req, res) => {
const subjectId = req.query.subjectId;
Subject.findOne({
subjectId: subjectId
}, function (err, subjects) => {
if (err) {
return res.status(400).json(err);
}
return res.status(200).json(subjects);
});
}
then I started writing like this using async but not await and call backs.
exports.getSubjectById = async (req, res) => {
const subjectId = req.query.subjectId;
Subject.findOne({
subjectId: subjectId
}, function (err, subjects) => {
if (err) {
return res.status(400).json(err);
}
return res.status(200).json(subjects);
});
}
then I finally wrote this async await and NO callbacks.
exports.getSubjectById = async (req, res) => {
const subjectId = req.query.subjectId;
const subject = await Subject.findOne({
subjectId: subjectId
});
if (!subject) {
return res.status(404).json({
error: "no subject found for this id"
})
} else {
return res.status(200).json(subject);
}
}
can anyone please tell me which one should I use as production best practice and difference between these methods?
In this case, number 3 is better, but you need to consider the exception case.
So, you need to add try catch when you call async function.
exports.getSubjectById = async (req, res) => {
const subjectId = req.query.subjectId;
try {
const subject = await Subject.findOne({
subjectId: subjectId
});
if (!subject) {
return res.status(404).json({
error: "no subject found for this id"
})
} else {
return res.status(200).json(subject);
}
} catch (error) {
// exception, maybe db is broken when query data
console.log(error);
return res.status(500).end("Exception")
}
}
And about don't use synchronous, there is an example you could take a look.
If I want api send the response after 5 second delay, what should I do?
In Java, you maybe use sleep(5) to delay.
So you might use following code when you write node.js.
exports.getSubjectById = async (req, res) => {
const startTime = new Date().getTime()
while(new Date().getTime() - startTime < 5000){}
res.status(200).send("Success")
}
It'll work, but it'll block the thread.
In node.js, you should use setTimeout to simulate the delay 5 seconds.
exports.getSubjectById = async (req, res) => {
setTimeout(() => {
res.status(200).send("Success")
}, 5000)
}
Above example, while loop is synchronous function.
But setTimeout is asynchronous function.
Due to the mechanism of node.js, you should use asynchronous function instead of synchronous function.
Solution number 3 is good and also, most importantly, correct practice. Accessing the database is asynchronous by itself, it returns a promise. If you use async / await, it will return exactly what you are requesting. Also you can write simple test, for example, the speed of execution for each...
I have the following Express endpoint:
const all = require('promise-all');
router.post('/verify', upload.single('photo'), async (req, res) => {
...
await all({'p1': p1, 'p2': p2}).then((response) => {
...
console.log("Response:",
ruleCtrl.manageRule(detection, res);
});
});
ruleCtrl.manageRuleis as follows:
export async function manageRule(identifierDetected, res) {
let rule = db.getRule(identifierDetected);
await all([rule]).then((ruleExtracted) => {
...
res.json(ruleExtracted);
}).catch((err) => {
res.status(418).send("DOCUMENT_NOT_RECOGNIZED");
});
}
and db.getRule:
export async function getRule(idRule) {
return new Promise((resolve, reject) => {
Rule.findOne({ruleID: idRule}, (err, rule) => {
if (err) {
reject("MongoDB Rule error: " + err);
} else {
resolve(rule);
}
});
})
}
My response is into manageRule and this function depends of the values extracted into the await all. So, right now, Express is returning a response before get the information from mongoose database (db).
Which is the way to handle this issue?
Thanks everyone!
I would refactor your code a bit to make it easier to read, and also return the result from ruleCtrl.manageRule(detection, res);.
The request might simply be timing out since your original code is missing a return there or an await (to make sure it finishes executing)
Express endpoint:
const all = require('promise-all');
router.post('/verify', upload.single('photo'), async (req, res) => {
...
// Catch any exceptions from the promises. This is the same as using .catch
try {
// Lets assign the returned responses to variable
let [p1Result, p2Result] = await all({'p1': p1, 'p2': p2});
...
console.log("Responses:", p1Result, p2Result);
// return the response from manageRule method
return ruleCtrl.manageRule(detection, res);
} catch(err) {
// Handle err here
}
});
One of the great benefits with async await is moving away from chained promises, so simply return the result from the await to a variable instead of using .then()
ruleCtrl.manageRule
export async function manageRule(identifierDetected, res) {
// Use try catch here to catch error from db.getRule. Assign to variable and return
// res.json
try {
let ruleExtracted = await db.getRule(identifierDetected);
...
return res.json(ruleExtracted);
} catch(err) {
return res.status(418).send("DOCUMENT_NOT_RECOGNIZED");
}
}
You dont have to return res.json or res.status here, I just like to keep track of when I want to end function execution.
You could refactor the ruleCtrl.manageRule method even further by not sending in res as a parameter but by returning the result from db.getRule instead. Let router.post('/verify) handle req and res, so to make it even easier to read.
I originally had try...catch in my getAllUsers method for querying but ended up removing it because as far as I could tell it wasn't doing anything. I know the async function returns a promise so it should be fine and actually based on how the code is structured I think it's required otherwise the try...catch in the query would swallow the error. Is there anything I'm missing with this structure and use of async/await, try...catch, and .then .catch?
let getAllUsers = async () => {
let res = await models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
return res;
};
router.get(`${path}`, (req, res) => {
queries.users.getAllUsers()
.then(users => {
res.status(200).json(users);
})
.catch(error => {
res.status(500).send(error)
});
});
There's just no reason to use await at all in your function. Instead of this:
let getAllUsers = async () => {
let res = await models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
return res;
};
It can just be this:
let getAllUsers = () => {
return models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
};
You just return the promise directly and the caller uses the promise the same as you already were. Since you are not using the result within your getAllUsers() function or coordinating it with anything else, there's no reason to use await. And, since there's no use of await, there's no reason for the function to be declared async either.
If you wanted to use await, you could use it for the caller of getAllUsers() like this:
router.get(`${path}`, async (req, res) => {
try {
let users = await queries.users.getAllUsers();
res.status(200).json(users);
} catch(error => {
res.status(500).json(error);
}
});
And, here you would have to use try/catch in order to catch rejected promises. Personally, I don't see how this is particularly better than what you had originally with .then() and .catch() so for a situation as simple as this (with no coordination or serialization with other promises), it's really just a matter of personal preference whether to use .then() and .catch() or await with try/catch.
You would use async/await with the code that calls getAllUsers rather than using it in getAllUsers itself:
const getAllUsers = () => {
return models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
};
router.get(`${path}`, async (req, res) => {
try {
const users = await queries.users.getAllUsers();
res.status(200).json(users);
} catch (error) {
res.status(500).send(error)
}
});
The best way I have found to handle this is using middleware.
Here is the function:
// based upon this
// http://madole.xyz/error-handling-in-express-with-async-await-routes/
// https://github.com/madole/async-error-catcher
export default function asyncErrorCatcher(fn) {
if (!(fn instanceof Function)) {
throw new Error("Must supply a function");
}
return (request, response, next) => {
const promise = fn(request, response, next);
if (!promise.catch) {
return;
}
promise.catch((error) => {
console.log(error.message);
response.sendStatus(500);
});
};
}
Here is the usage:
router.get("/getSettings/", asyncErrorCatcher(async (request: Request, response: Response) => {
const settings = await database.getSettings();
response.json(settings);
}));