async.parallel for app.use 第二个参数

async.parallel for app.use with second parameter

我使用 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);

您陷入了困境,您应该稍微改变一下您的方法,目前您已经完全把一个简单的问题复杂化了,您正在寻找的解决方案在 [=39= 中是不可能的].

如果你想在 express 中创建路由,那么不要对所有东西都使用 app.use,它应该只用于中间件或注册一个路由器,你可以在其上定义路由。

您应该使用:

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

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

app.use(router)

...

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

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

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

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

你还应该注意不要乱用 express 的中间件,因为你会弄乱现有的中间件(比如错误路由)。

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

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

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

概念中的中间件

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

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

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

> httpContext.middleware

  > metricsMiddleware

    > rtEndMiddleware

      > trackingContextMiddleware

        /healthRoutes

      < trackingContextMiddleware

    < rtEndMiddleware

  < metricsMiddleware

< httpContext.middleware

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

中间件 - 是否可并行化?

根据博客post中的并行化,之前分开的中间件(getUsergetSiteListgetCurrentSitegetSubscription)很可能运行独立的,在我看来,这使得它们非常不适合用作中间件。难怪他们观察到显着的性能提升,因为这些项目 本来就不应该 运行 串联 。如果我们使用 post 中相同的 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 参数传递
    • 关掉所有这些 Promise 落后于 Promise.all,最后
    • 在 gated 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() 都会将一个项目添加到中间件堆栈中,你基本上不需要做任何事情来并行化所有中间件到那时,如果我沿着这条路走下去,我会将中间件与路由实现本身明确分开(无论是并行使用还是串行使用),以明确特定应用程序逻辑的开始位置。