Passing in Async functions to Node.js Express.js router

29,454

Solution 1

May be you didn't found results because async/await is an ES7 not ES6 feature, it is available in node >= 7.6.

Your code will work in node. I have tested the following code

var express = require('express');
var app = express();

async function wait (ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms)
  });
}

app.get('/', async function(req, res){
  console.log('before wait', new Date());
  await wait(5 * 1000);
  console.log('after wait', new Date())
  res.send('hello world');
});

app.listen(3000, err => console.log(err ? "Error listening" : "Listening"))

And voila

MacJamal:messialltimegoals dev$ node test.js 
Listening undefined
before wait 2017-06-28T22:32:34.829Z
after wait 2017-06-28T22:32:39.852Z
^C

Basicaly you got it, you have to async a function in order to await on a promise inside its code. This is not supported in node LTS v6, so may be use babel to transpile code. Hope this helps.

Solution 2

Update

Since ExpressJs 5, async functions are supported, and throw errors as expected

Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error
source


In Express 4 or less, it sort of works, but not really

While it seems to work, it stops handling errors thrown inside the async function, and as a result, if an error is not handled, the server never responds and the client keeps waiting until it timeout.

The correct behavior should be to respond with a 500 status code.


Solutions

express-promise-router

const router = require('express-promise-router')();

// Use it like a normal router, it will handle async functions

express-asyncify

const asyncify = require('express-asyncify')

To fix routes set in the app object

Replace var app = express(); with

var app = asyncify(express());

To fix routes set in router objects

Replace var router = express.Router(); with

var router = asyncify(express.Router());

Note

You only need to apply the asyncify function in the objects where you set the routes directly

https://www.npmjs.com/package/express-asyncify

Solution 3

I think you can't do it directly because exceptions are not caught and the function won't return if one is thrown. This article explains how to create a wrapper function to make it work: http://thecodebarbarian.com/using-async-await-with-mocha-express-and-mongoose.html

I haven't tried it but was investigating this recently.

Solution 4

Use express-promise-router.

const express = require('express');
const Router = require('express-promise-router');
const router = new Router();   
const mysql = require('mysql2');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'myusername',
  password: 'mypassword',
  database: 'mydb',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
}).promise();

router.get('/some_path', async function(req, res, next) {
  const [rows, ] = await pool.execute(
    'SELECT * ' +
    'FROM mytable ',
    []
  );

  res.json(rows);
});

module.exports = router;

(The above is an example of using mysql2's promise interface with express-promise-router.)

Solution 5

Express 5 will automatically handle async errors for you

https://expressjs.com/en/guide/error-handling.html currently says it clearly:

Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:

app.get('/user/:id', async function (req, res, next) {
 var user = await getUserById(req.params.id)
 res.send(user)
})

If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.

We can test that as follows:

const assert = require('assert')
const http = require('http')

const express = require('express')

const app = express()
app.get('/error', async (req, res) => {
  throw 'my error'
})

const server = app.listen(3000, () => {
  // Test it.
  function test(path, method, status, body) {
    const options = {
      hostname: 'localhost',
      port: server.address().port,
      path: path,
      method: method,
    }
    http.request(options, res => {
      console.error(res.statusCode);
      assert(res.statusCode === status);
    }).end()
  }
  test('/error', 'GET', 500)
})

The terminal output on [email protected] is the expected:

500
Error: my error
    at /home/ciro/test/express5/main.js:10:9
    at Layer.handle [as handle_request] (/home/ciro/test/node_modules/router/lib/layer.js:102:15)
    at next (/home/ciro/test/node_modules/router/lib/route.js:144:13)
    at Route.dispatch (/home/ciro/test/node_modules/router/lib/route.js:109:3)
    at handle (/home/ciro/test/node_modules/router/index.js:515:11)
    at Layer.handle [as handle_request] (/home/ciro/test/node_modules/router/lib/layer.js:102:15)
    at /home/ciro/test/node_modules/router/index.js:291:22
    at Function.process_params (/home/ciro/test/node_modules/router/index.js:349:12)
    at next (/home/ciro/test/node_modules/router/index.js:285:10)
    at Function.handle (/home/ciro/test/node_modules/router/index.js:184:3)

If you visit it on the browser, you will see an HTML page that says my error.

If you run the exact same code on [email protected], you see on the terminal only:

UnhandledPromiseRejectionWarning: my error

but not the 500 nor my error. This is because the request just hangs forever. If you try to open it on the browser, you will see it hang more clearly.

TODO: how to make it show the stack trace as well instead of just my error? Getting the stack trace in a custom error handler in Express?

Express 4 solution

The simplest solution for Express 4 is to just wrap every single route in a try/catch as follows:

app.get('/error', async (req, res, next) => {
  try {
    throw new Error('my error'
    res.send('never returned')
  } catch(error) {
    next(error);
  }
})

which produces the same correct behavior as Express 5.

You can also factor this out further with some of the methods discussed at: express.js async router and error handling

Tested on Node.js v14.16.0.

Share:
29,454
ForgetfulFellow
Author by

ForgetfulFellow

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." --Brian Kernighan and P.J. Plauger, The Elements of Programming Style

Updated on January 19, 2022

Comments

  • ForgetfulFellow
    ForgetfulFellow over 2 years

    This seems like a straightforward google, but I can't seem to find the answer...

    Can you pass in ES6 ES7 async functions to the Express router?

    Example:

    var express = require('express');
    var app = express();
    
    app.get('/', async function(req, res){
      // some await stuff
      res.send('hello world');
    });
    

    If not, can you point me in the right direction on how to handle this problem ES7 style? Or do I just have to use promises?

    Thanks!

  • Arpit Solanki
    Arpit Solanki almost 7 years
    What does this have to do with the question asked. Exceptions are not the point of question here although if you had explained something about passing async function then it would be great.
  • Zalaboza
    Zalaboza almost 6 years
    This is very important side effect, it will male express swollo errors and not show them and request will never return causing timeout for client
  • Madacol
    Madacol about 4 years
    WARNING: If you use async functions directly, express will stop handling errors and will not respond with a 500 status code. In fact, if an error occurs, express will not respond at all, and the client will keep waiting until it timeout.
  • laxman
    laxman almost 4 years
    this is fixed for v5, from docs, "Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error."
  • Michael Bushe
    Michael Bushe over 3 years
    Any answer that doesn't consider the exceptional case is not an answer.
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com almost 3 years
    Note that Express 5 will start handling the async errors correctly: stackoverflow.com/a/67689269/895245
  • milos
    milos over 2 years
    @MichaelBushe everything is an answer and it can help you understand the problem