【问题标题】:async.parallel for app.use with second parameter带有第二个参数的 app.use 的 async.parallel
【发布时间】:2021-12-13 09:11:14
【问题描述】:

我使用express v4.17.1 并想让我的 app.use() 中间件并行运行。

所以我在网上找到了一些例子。

示例:

function runInParallel() {
  async.parallel([
    getUserProfile,
    getRecentActivity,
    getSubscriptions,
    getNotifications
  ], function(err, results) {
    //This callback runs when all the functions complete
  });
}

但我的应用程序中的内容是:

const app = express();
const APP_FOLDER = "bdt";

app.use(httpContext.middleware);
app.use(metricsMiddleware);
app.use(rtEndMiddleware);
app.use(trackingContextMiddleware);
app.use(healthRoutes());

app.use("/" + APP_FOLDER + "/api/products", productsRoutes);
app.use("/tcalc/" + APP_FOLDER + "/api/products", productsRoutes);

productRoutes 是这样的:

const jsonParser = bodyParser.json({
  limit: "1mb",
});

const accessFilter = accessFilterMiddleware(Registry.list());
const localDevFilter = localDevFilterMiddleware(Registry.list());

const apiRoutes: Router = Router();


apiRoutes.get("/", listProducts);
apiRoutes.get("/healthz", cProductHealth);
apiRoutes.get("/:id", accessFilter, localDevFilter, fetchProductData);
apiRoutes.post(
  "/:id",
  accessFilter,
  localDevFilter,
  jsonParser,
  fetchProductData,
);
apiRoutes.get(
  "/:id/fields/:fieldId/options",
  accessFilter,
  localDevFilter,
  fetchProductOptions,
);
apiRoutes.post(
  "/:id/loadmoreoptions",
  accessFilter,
  localDevFilter,
  jsonParser,
  loadMoreOptions,
);
apiRoutes.post("/:id/ploy", accessFilter, jsonParser, fetchMultipleProductData);
apiRoutes.post(
  "/:id/gxx",
  accessFilter,
  localDevFilter,
  jsonParser,
  fetchGssData,
);
apiRoutes.get("/:id/healthz", collectProductHealth);

我认为对于第一个应该很容易:

async.parallel([
  httpContext.middleware,
  metricsMiddleware,
  rtEndMiddleware,
  trackingContextMiddleware,
  healthRoutes()
], function(err, results) {
  //This callback runs when all the functions complete
});

但我的问题是:在这种情况下,如何使用第二个参数(productRoutes)来做到这一点?

app.use("/" + APP_FOLDER + "/api/products", productsRoutes);
app.use("/tcalc/" + APP_FOLDER + "/api/products", productsRoutes);

【问题讨论】:

  • “想让我的路线并行运行”请解释一下
  • 为什么不创建一个路由并使用 promise all 从多个服务中获取结果。 developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/…
  • 您要并行运行的四个中间件必须以某种方式协作,以便为每个传入请求 req 生成 一个 响应 res。你能解释一下他们的合作将如何运作吗?例如,每个中间件都可以贡献一些 HTML,但最终会在整个响应中以随机顺序结束。
  • 我已更新我的问题以更准确。 @HeikoTheißen

标签: javascript express async.js


【解决方案1】:

你已经陷入困境,你应该改变你的方法相当多,目前你完全把一个简单的问题复杂化了,你正在寻找的解决方案在 javascript 中是不可能的。

如果你想在 express 中创建路由,那么不要将app.use 用于所有内容,它应该只用于中间件或注册路由器,您可以在其上定义路由。

你应该使用:

app.get('/', () => ...

定义您的路线。或者,您也可以通过以下方式使用路由器:

app.use(router)

...

router.get('/', () => ...

如果您想在 javascript 中定义 async 或“并行”路由,那么只需像往常一样定义异步回调,删除您已经完成的大部分内容。

app.get('/', async () => ...

现在是一个异步执行的路由。

您还应该小心,不要乱用 express 中间件,因为您会弄乱现有的中间件(例如错误路由)。

更重要的是,您所指的库只是一个具有简洁功能的辅助库,它不会从根本上改变 javascript 的工作方式。当您调用异步函数时,它将被添加到事件队列中,并且仍然以串行的方式一个接一个地同步执行,真正的多线程是不可能的,除了服务工作者和浏览器 API 调用临时在它们自己的单个线程中执行, 在被添加到事件队列之前。

您正在寻找的只是一个简单的:router.get('/', async () => ...,这是您能做的最好的事情,看起来您的所有路由都在并行执行。

在您声明了其中的多个之后,您可以使用Promise.all 之类的内容调用所有这些。我最好的猜测是parallel 之类的东西也在做。

【讨论】:

  • That is now a route that will execute asynchronously. - 当 a) 在函数中没有使用 await 和 b) 函数已经在继续运行时,这种变化(单独添加 async)作用很小传递风格(与 Express 路线一样,通过 res 参数)。
  • @msbit 我已将我的问题更新为更准确,您能看看吗?
  • @Gutelaunetyp 正如我在回答中提到的那样,除非该库将请求向下传递到内核并为其创建实际线程,否则您将永远无法让您的路线真正并行,我对此表示怀疑。即使您的库使所有路由异步,它仍将被添加到事件队列中,您只是将串行执行从一个地方移动到另一个地方。
  • 不可能以快 10 倍的速度获得后端请求,express 已经在 10 毫秒内处理请求。该博客展示了缓存、前端资源和其他东西的性能,这些都是提高性能的东西。您尝试在错误的地方进行优化。
  • 如果你想让你的东西真正并行,然后使用 nginx 作为旁路,就像你有一天会在生产中使用它一样,那么它将是真正的多线程请求。你不会在快递中实现你想要的。
【解决方案2】:

概念中的中间件

据我了解,中间件类似于链条,其中每个中间件项:

  • 执行一些初始逻辑
  • 调用链中的下一个链接,然后
  • 执行一些最终逻辑

这两个逻辑块都是可选的,但调用下一个链接不是。例如,使用您的第一组中间件,一直到healthRoutes(),它可以像这样可视化:

> httpContext.middleware

  > metricsMiddleware

    > rtEndMiddleware

      > trackingContextMiddleware

        /healthRoutes

      < trackingContextMiddleware

    < rtEndMiddleware

  < metricsMiddleware

< httpContext.middleware

通常使用这种链式结构是因为每个中间件可能会增强请求的公共状态,有时有条件地基于先前执行的中间件的“输出”,并且对于许多中间件而言,初始逻辑需要在主体之前执行请求,以及之后的最终逻辑。

中间件 - 是否可并行化?

基于博文中的并行化,以前独立的中间件(getUsergetSiteListgetCurrentSitegetSubscription)很可能独立运行,在我看来,这使得它们成为非常差的候选用作中间件。难怪他们观察到了显着的性能改进,因为这些项目一开始就不应该连续运行。如果我们使用帖子中相同的 parallel() 函数(注意,特别是该函数,而不是 async.parallel),对于与上述相同的中间件,执行看起来更像:

> httpContext.middleware

< httpContext.middleware

> metricsMiddleware

< metricsMiddleware

> rtEndMiddleware

< rtEndMiddleware

> trackingContextMiddleware

< trackingContextMiddleware

/healthRoutes

因此,并行化中间件会显着改变执行顺序。只有您可以确定这对于您的应用程序是否可以接受,但我可以想象metricsMiddlewaretrackingContextMiddleware 在请求执行之前和之后都可能需要执行一些逻辑。

快速路由语法

如果您决定要并行化部分或全部中间件,我建议直接利用原生 Promises,而不是单独的库,并执行以下操作:

const parallel = (...middlewares) => (req, res, next) => {
  Promise.all(middlewares.map(x => new Promise((resolve, reject) => {
    x(req, res, resolve);
  }))).then(() => next());
}

这个函数的重要部分是:

  • 返回一个接受reqresnext参数的函数,满足Express中间件的要求,即
    • 将每个中间件映射到执行中间件的Promise,并将Promise回调的resolve参数作为其next参数传递
    • 将所有这些Promises 门在Promise.all 后面,最后
    • 当门控Promise 解析时调用外部提供的next 参数

然后,如果您决定让 httpContent.middlewaremetricsMiddleware 并行运行,而其他串行运行,您可以这样使用:

app.use(parallel(
  httpContext.middleware,
  metricsMiddleware
));
app.use(rtEndMiddleware);
app.use(trackingContextMiddleware);
app.use(healthRoutes());

其中的执行可以被可视化为:

> httpContext.middleware

< httpContext.middleware

> metricsMiddleware

< metricsMiddleware

> rtEndMiddleware

  > trackingContextMiddleware

    /healthRoutes

  < trackingContextMiddleware

< rtEndMiddleware

至于剩下的两个app.use() 语句,鉴于每个后续的app.use() 都会将一个项目添加到中间件堆栈中,您基本上不需要做任何事情来并行化所有中间件,如果我沿着这条路走,我会明确地将中间件与路由实现本身分开(无论是并行使用还是串行使用),以明确具体应用程序逻辑的开始位置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 2013-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-26
    • 1970-01-01
    相关资源
    最近更新 更多