express-graphql后置中间件执行问题求助
我之前也踩过express-graphql的这个坑,确实它的设计里没有调用Express的next()方法,导致常规的中间件顺序没法让后续逻辑在Resolver执行完后触发。咱们来一步步拆解问题和解决方案:
为什么常规中间件方案不生效?
express-graphql的核心处理逻辑是在自身中间件内完成请求响应的,它并没有调用next()来传递中间件链。这意味着,在它之后注册的中间件根本不会被执行——因为响应已经被发送,Express的中间件流程到这里就直接结束了。
变通方案里Mutation数据不即时更新的原因
你提到用变通方案时,Mutation修改的数据在回调里拿不到最新值,只有用setTimeout包裹才行?这是因为GraphQL的Resolver通常包含异步操作(比如数据库更新),而你的回调可能在Resolver的异步任务还没完成时就执行了。setTimeout把代码放到了事件循环的下一轮,这时候Resolver的异步操作刚好完成,所以能拿到最新数据,但这本质上是靠“碰时机”,非常不可靠。
可行的解决方案
方案1:通过GraphQL Context传递回调
在创建express-graphql实例时,给context传入一个回调函数,然后在Resolver(尤其是Mutation的Resolver)执行完成后手动调用这个回调。这样能确保回调逻辑在Resolver的所有异步操作完成后触发:
const express = require('express'); const { graphqlHTTP } = require('express-graphql'); const app = express(); // 定义你的Schema和Resolver const yourSchema = /* ... */; const mutationResolvers = { updateUser: async (parent, args, context) => { // 执行异步更新操作,比如数据库修改 const updatedUser = await UserModel.findByIdAndUpdate(args.id, args.data, { new: true }); // 调用context里的回调,确保在异步操作完成后执行 context.postResolverCallback(); return updatedUser; } }; app.use('/graphql', graphqlHTTP({ schema: yourSchema, graphiql: true, context: { postResolverCallback: () => { // 这里写你想在Resolver之后执行的逻辑 console.log('Resolver执行完毕,可以处理后续操作了'); // 这里能安全获取最新数据,因为Resolver的异步任务已经完成 } } }));
方案2:替换为apollo-server-express
如果项目允许的话,更推荐换成Apollo Server的Express集成版本。它支持请求生命周期的插件机制,其中willSendResponse事件就是在Resolver执行完成、准备发送响应时触发的,完全符合你的需求:
const express = require('express'); const { ApolloServer } = require('apollo-server-express'); const app = express(); const yourSchema = /* ... */; const server = new ApolloServer({ schema: yourSchema, plugins: [ { async willSendResponse(requestContext) { // 这里编写Resolver执行后的逻辑 // requestContext包含请求、响应、Resolver执行结果等信息 console.log('Resolver执行完成,即将发送响应'); // 这里可以放心获取最新数据,因为所有Resolver任务都已完成 } } ] }); // 启动Apollo Server并挂载到Express await server.start(); server.applyMiddleware({ app }); app.listen(4000, () => { console.log('Server running on http://localhost:4000/graphql'); });
Apollo Server的生态比express-graphql更活跃,后续维护也更有保障,这是长期来看更优的选择。
临时修复现有变通方案(不推荐)
如果暂时不想更换库,那要确保你的回调逻辑是在Resolver的异步操作完成后执行的,绝对不要依赖setTimeout。可以通过在Resolver里显式通知回调,或者确保express-graphql的响应发送时机是在所有异步任务完成后——不过这种方式不如前两种方案可靠,容易出现时序问题。
内容的提问来源于stack exchange,提问作者zagen

