Do you use Promise-based data fetching in your Express apps? Are you sick of nesting route-handling code statements inside .then()
method calls? Want to learn async/await
in a practical, applicable way? If your answer was "yes!" to any of those questions, read on, and I will help you move those Promises into asynchronous middleware functions.
(If you just want to see my example repository with finished code, check it out here)
The Problem
If you are anything like me, your Express router fetches data by requesting data from the respective API endpoint or database using a Promise
-based approach. This is a modern approach to alleviate a problem that many developers call "callback hell".
The following snippet is what modeled after one of my current Express side projects; note the asynchronous, Promise-based structure of passing data in lieu of synchronous, function-based callbacks.
/* Example of a Promise-based data request approach */
const endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
app.get('/',(req,res) => {
PromiseBasedDataRequest(endpoint).then(data => {
const { title, body } = data;
req.render('post', { title, body });
})
})
So we're using relatively flat Promises versus a pyramid of callback functions on top of callback functions, great!
But once we have our data passed through the chain of .then()
calls, we now have to write a bunch of code to process the data and/or send the actual response for the router request. Some developers will handle their code this way, and if that works for you, fantastic; however, now that async/await is supported in Node, it doesn't have to be this way.
My Solution
With support for asynchronous functions (often referred to as async/await
) in Node.js as of v7.6, we can now extract data directly from a resolved Promise
in an async
middleware function and pass that data to the final router callback in a clean and easily-readable manner.
Consider the following middleware function as an update to the previous snippet:
const endpoint = 'https://jsonplaceholder.typicode.com/posts/1';
const asyncMiddleware = async (req,res,next) => {
const data = await PromiseBasedDataRequest(endpoint);
req.data = data.json()
next()
}
You may noticed we added a few things:
- The
async
keyword in front of the function declaration; this indicates to the engine that somewhere in the function body, there is a call to a Promise, and often that a Promise uses theawait
keyword (see #2). - The
await
keyword in front of thePromiseBasedDataRequest
call assigned to thedata
variable; this indicates to the engine that all other code execution should be halted until the Promise resolves. (i.e. the application "awaits" the result of the request) This also allows thePromiseValue
to be assigned to a variable and used later.
After that, the data
fetched from endpoint
is assigned to a property on the req
/request object; this allows the value to be accessed later on in the application via req.data
. Finally, a call to next()
sends the req
and res
objects to the next piece of middleware, or, when no other middleware is in place, to the final route callback.
Let's put our asyncMiddleware
function in the router's chain; in Express, this happens between the route and the final callback. (you can place as many middleware functions here as you'd like, just do not forget to call next()
at the end of each!)
app.get('/', asyncMiddleware, (req,res) => {
const { title, body } = req.data;
req.render('post', { title, body });
})
Wow! So fresh, so clean.
Now we have a very flat and readable router declaration, with the bulk of our code that would normally sit in the router callback^[1] instead situated in a middleware function, and the data being synchronously passed into the final router callback function.
If you would like to see this implementation/pattern in a "completed" project, check out the example repo I set up for this article. Follow the instructions in the README file to get started; if you find something wrong or need help, feel free to file an issue and I will be happy to review with you.
The Motivation
I was motivated to research this solution because I wanted to learn and experiment more with async
/await
functions and Express middleware; the best way I learn is by making test projects and having hands-on experience implementing features.
I plan to use this exact pattern of writing async middleware in a side project that utilizes the Javascript Contentful SDK and MySQL driver. The current implementation I have in this project is exactly like the first code snippet example: the respective API is called and wraps the actual res
/response call in either a callback or a .then()
method call. By rewriting these features into middleware functions, I aim to simplify the writing of new router paths/API endpoints and hopefully increase code reusability.
Conclusion
In closing, utilizing async
/await
as your Express middleware implementation helps keep your code reusable, readable, and up-to-date with current coding conventions.
I hope my little Saturday experiment helped you better understand asynchronous functions and Express middleware. Thanks for reading!
Further Reading
Notes
[1]: According to this Stack Overflow post, unresolved Promises do not pose any serious threat of a memory leak.
Top comments (12)
If switching frameworks in an option you might wanna check out koa v2 (koajs.com). koa v1 used generator functions, but koa v2 comes with a 100% promise-based API, so you have support for async/await route handlers and middlewares out of the box!
Express probably has the most evolved middleware ecosystem of all node server frameworks, but koa's is quite comprehensive as well :)
There is one thing in your code which may break: (sorry if my english is not good)
Take a look at this code:
If you run it, you will never see console.log's
And the same is true for you middleware code
If PromiseBasedDataRequest will be rejected then you never reach next() and execution will hang.
Rule of thumb here is simple: when we trying to connect Promise-style and callback-style code and you are somewhere in callback, you should always write .catch() block.
You don't need the "then" since the await do the job.
Also you don't need the next() at the end, if I'm not in a mistake, async express just go next when finish solving all the promises inside the function.
I'm in the mobile now but try to put an example later if you want
Thanks! I updated my snippets.
Regarding the
next()
call, I'll have to check that out.I mean this:
const endpoint = 'jsonplaceholder.typicode.com/posts/1';
const asyncMiddleware = async (req,res) => {
const data = await PromiseBasedDataRequest(endpoint);
req.data = await data.json() // if I remember well data.json() its a
//promise too thats why about another
//await
return req.data
}
// the workflow continues
Async await it's almost have a "syncronous" code.
Thanks for the snippet, exactly what i was looking for
I've adopted this library for automatically adding async/promise support in express routes, curious if you've looked at it and what your thoughts are -
npmjs.com/package/express-promise-...
Great article! Thanks :)
I noticed you have: req.render('post', { title, body });
shouldn't it be: res.render('post', { title, body }); ?
Good catch! I'll update my snippets
found it super helpful. Thanks
This was extremely helpful. Thanks
Thanks your post.
As I know we need try/catch to ensure safety in async/await function so that have you know anyway to better manually put try/catch in each middeware function ?