Sergio Romero
Sergio Romero

Reputation: 6607

Using promises, handlebars and middleware in ExpressJS

I have a handlebars template that contains two partials and a single piece of data that gets populated from the context.

<h1>Welcome to {{title}}</h1>
<div id="views">
    <div id="place">{{> place}}</div>
    <div id="player">{{> player}}</div>
</div>

Now on an ExpressJS route I am doing the following:

var express = require('express');
var router = express.Router();

var gameDal = require('../app/DAL/gameRepository');

router.use('/', (req, res, next) => {
    if(!res.locals.gameContext) res.locals.gameContext = {};

    gameDal.getGame(0).then(game => {
        res.locals.gameContext.player = game.player;
        res.locals.gameContext.place = game.place;
        req.gameTitle = game.title;
        next();
    });
});

router.get('/', (req, res) => {
    res.render('home', { "title": req.gameTitle });
});

module.exports = router;

The code as is working as expected but, if I take the "next()" statement out of the "then" callback, both partials are populated correctly with the data received but the "gameTitle" value is undefined.

In other words the following will not replace the {{title}} value in the template because by the time the template is rendered, the req.gameTitle value is "undefined":

router.use('/', (req, res, next) => {
    if(!res.locals.gameContext) res.locals.gameContext = {};

    gameDal.getGame(0).then(game => {
        res.locals.gameContext.player = game.player;
        res.locals.gameContext.place = game.place;
        req.gameTitle = game.title;
    });

    next();
});

So my questions are:

  1. How come the partials are populated with the data and the containing template is not?
  2. What would be the implications of keeping the "next()" statement inside of the promise callback?
  3. What if the promise gets rejected?

Thank you.

Upvotes: 0

Views: 1099

Answers (1)

jfriend00
jfriend00

Reputation: 707486

next() allows the routing to continue. If you allow that routing to continue BEFORE you've correctly populated the res.locals, then rendering will probably occur before those values are set. That would clearly be wrong.

So, you only want to call next() when your work is done in the middleware and everything is set up for rendering (or for the next middleware in the chain).

How come the partials are populated with the data and the containing template is not?

If some things happen to work and others do not, you may just be running into a "luck of the draw" timing issue. When you don't wait to call next(), you are creating a race between your async operation and other async operations involved in the rendering. Since those types of races are unpredictable, it may work, it may not or some part of it may work. The idea with correct code is to remove all races, so it always works.

What would be the implications of keeping the "next()" statement inside of the promise callback?

That's where it belongs (inside the promise callback) for proper and predictable execution. Only when that callback executes do you truly have everything ready for the next steps in rendering so only then should you call next() and continue routing.

What if the promise gets rejected?

You MUST have a reject handler and decide what the proper response is. If you put the next() inside the .then() handler where it belongs and you do not also have a reject handler, then your request will just never send a response and eventually the browser will time out. You need a .catch() handler that probably returns a 500 type error page. You can call next(err) where err is some sort of error and you can then have a generic error handling middleware that will offer an error page. See this answer for info about generic error handling with Express:

Error Handlers in Express

For example, you could do something like this:

router.use('/', (req, res, next) => {
    if(!res.locals.gameContext) res.locals.gameContext = {};

    gameDal.getGame(0).then(game => {
        res.locals.gameContext.player = game.player;
        res.locals.gameContext.place = game.place;
        req.gameTitle = game.title;
        next();
    }).catch(err => {
        next(err);
    });

});

// Generic error handler for express - should be last middleware defined
// on app object
// Note that this has four arguments compared to regular middleware that
// has three arguments
// This will handle the next(err) call
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
});

Upvotes: 2

Related Questions