【问题标题】:Parallel async routers in Node ExpressNode Express 中的并行异步路由器
【发布时间】:2018-01-29 16:16:22
【问题描述】:

我有一个快速服务器,它需要为每个请求从多个外部来源获取一些数据。这个逻辑被分成多个路由器(有些不是我管理的)。

这些路由器是完全独立的,因此不需要一个等待另一个。


例如,我有以下代码:

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!'));

当我通常在我的应用程序中使用所有这些路由器时,它将按顺序执行并在 6 秒内打印All done!。

但是为了提高页面的速度,我想并行执行它们,所以它们都在 2 秒内完成。我该怎么做?

【问题讨论】:

    标签: javascript node.js express routing


    【解决方案1】:

    这里绝对不需要单独的路由,您可以只使用一条路由并使用Promise.all 等待所有请求。如果它们不自然地支持 Promises,您可以将它们包装在一个中,例如

    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!');
    });
    

    根据 cmets 中的讨论,如果您无法将路线合并为一条路线 - 没关系,而且并不重要。考虑更多这一点,您真正需要的是一个包装器来捕获“真正的”中间件,将其添加到队列中,然后移至下一个。为此,您至少需要访问设置路线的代码(它似乎有),例如

    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
        };
    }
    

    然后在你的路线中

    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!');
    }
    

    【讨论】:

    • 感谢您的回答。这就是我的想法,但正如我在问题中提到的,有些路由器不是由我管理的。所以我希望找到一种使用现有路由器的方法。
    • @DuncanLuk 抱歉,只能继续给出的示例,您对路线有任何的控制吗?在不知道错综复杂的情况下使用 3rd 方代码做任何事情是相当棘手的。
    • 它不是第 3 方,而是遗留代码。它使用并更改请求和响应对象,这就是为什么它首先是路由器。它也被用于多个地方,所以如果可能的话,我宁愿不必重构所有东西。
    • 如果不是第 3 方,那么您是否有权访问代码?不幸的是,如果不对现有路由进行一些更改,我看不出如何解决此问题,因为这完全取决于它们与next 的交互方式。
    • @DuncanLuk 我会更新我的答案,Jonas 不确定我是否会对这样的代码投入生产感到高兴 - 看起来它可能会很快成为维护问题。
    【解决方案2】:

    您可以向所有子路由器发出虚假请求(通过this 答案发现的内部 handle 方法):

    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"));
    });
    

    这也可以通过在请求中传递一个 promise 数组来手动实现,然后你可以在最后等待它们:

    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")));
    

    【讨论】:

    • 看起来这可行,但我担心有一天这会破坏我的代码,因为框架很可能不支持这种“hack”。
    猜你喜欢
    • 2018-01-10
    • 2020-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    相关资源
    最近更新 更多