Duncan Lukkenaer
Duncan Lukkenaer

Reputation: 13994

Parallel async routers in Node Express

I have an express server which needs to fetch some data from multiple external sources for each request. This logic is seperated into multiple routers (some are not managed by me).

These routers are completely independent, so there is no need for one to wait on the other.


As an example I have the following code:

const router1 = express.Router();
const router2 = express.Router();
const router3 = express.Router();
const finalRouter = express.Router();

router1.use((req, res, next) => setTimeout(next, 2000));
router2.use((req, res, next) => setTimeout(next, 2000));
router3.use((req, res, next) => setTimeout(next, 2000));

finalRouter.use((req, res, next) => console.log('All done!'));

When I would normally use all these routers in my application, it will execute sequentially and print All done! in 6 seconds.

But to improve the speed of my page I want to execute them in parallel, so they are all finished in 2 seconds. How can I do this?

Upvotes: 0

Views: 1073

Answers (2)

James
James

Reputation: 82136

Individual routes definitely aren't needed here, you could just have a single route and use Promise.all to wait on all the requests. If they don't naturally support Promises, you could just wrap them in one e.g.

router.use(async (req, res, next) => {
    await Promise.all([
       new Promise((resolve, reject) => {
           setTimeout(() => resolve(), 2000);
       }),
       ...
    ]);
    next();
});
router.use((req, res, next) => {
    console.log('All done!');
});

As per the discussion in the comments, if you don't have the ability to merge the routes into one - that's fine, and doesn't really matter. Thinking more about this, all you would really need is a wrapper to capture the "real" middleware, add it to a queue, and then move onto to the next. For this to work, you would at least need access to the code that set's up the routes (which it appears to have) e.g.

queue.js

module.exports = route => {
    return (req, res, next) => {
        // ensure we've got a queue
        if (!(req.queue instanceof Array)) {
            req.queue = [];
        }
        // queue the blocking route
        req.queue.push(new Promise((resolve, reject) => {
            // run real route, signal success or failure when done
            route(req, res, err => err ? reject(err) : resolve()); 
        });
        next(); // move on, don't wait
    };
}

Then in your routes

const queue = require('./queue');
...
router.use(queue((req, res, next) => setTimeout(() => next(), 2000))));
router.use(queue((req, res, next) => setTimeout(() => next(), 2000))));
router.use(queue((req, res, next) => setTimeout(() => next(), 2000))));
router.use(async (req, res, next) => {
    await Promise.all(req.queue);
    console.log('All done!');
}

Upvotes: 2

Jonas Wilms
Jonas Wilms

Reputation: 138457

You could do fake requests to all subrouters ( by abusing the internal handle method as found out by this answer):

const mainroute = Express.Router();

mainroute.get("/",function(req,res,next){     
 Promise.all([
  new Promise(resolve => 
    router1.handle(req,res, resolve)),
  new Promise(resolve => 
    router2.handle(req,res,resolve))
  ]).then(_=>res.end("all done"));
});

that could also be achieved manually by passing a promise array with the request, then you can await them all at the end:

const router1 = express.Router();
const router2 = express.Router();

const timer = ms => new Promise( res => setTimeout(res,ms));

router1.use((req, res, next) =>{
  req.promises = [timer(2000)];
});      
router1.use( req => req.promises[0] = req.promises[0].then(_=>"first done"));

router2.use((req, res, next)=>req.promises.push(timer(2000)));

router1.use("/test",router2);
router1.use((req,res)=> Promise.all(req.promises).then(_=>res.end("all done")));

Upvotes: 0

Related Questions