如何在响应GET请求前等待第三方服务器的POST回调
问题
我有一台用于封装API并处理前端额外需求的服务器。每当服务器收到请求时,会先完成自身处理,再向一台不受我控制的第三方服务器发送POST请求。该请求处理耗时较长,第三方服务器支持通过传入callback_url,在处理完成后向该地址发送POST回调。
当前代码中,GET接口/my_url发起请求后会立即返回“处理已启动”的响应,但我需要让该GET接口等待/callback_here收到第三方的POST回调后,完成后续处理再向前端返回最终响应。请问该如何实现?有没有更优的方案?
现有代码示例
app.get(`/my_url`, async (c) => { // 执行一些前置处理 const formData = new FormData() // 添加其他所需参数 formData.append(`callback_url`, `https://my-url.com/callback_here`) const response = await fetch(`external-url.com`, { method: `POST`, body: formData, }).then(response => response.json()) // 现在会立即返回“处理已启动”的响应 }) app.post(`/callback_here`, async (c) =>{ // 第三方处理完成后会向这里发送POST请求 })
解决方案
方案一:基于内存的请求等待(适合小流量场景)
核心思路是用全局Map存储每个请求的Promise resolver,让/my_url接口等待对应回调触发后再返回响应。
实现代码
// 全局存储请求的回调 resolver,key用唯一请求ID const pendingRequests = new Map() app.get(`/my_url`, async (c) => { // 生成唯一请求ID,这里用Node.js内置的crypto模块生成UUID const requestId = crypto.randomUUID() // 创建Promise,将resolver存入全局Map const waitForCallback = new Promise((resolve) => { pendingRequests.set(requestId, resolve) }) const formData = new FormData() // 把requestId附加到callback_url上,让第三方回调时带回 formData.append(`callback_url`, `https://my-url.com/callback_here?requestId=${requestId}`) // 发送请求给第三方 await fetch(`external-url.com`, { method: `POST`, body: formData, }) // 等待回调触发 const callbackData = await waitForCallback // 执行后续处理逻辑 const finalResult = handleCallbackData(callbackData) // 返回最终响应给前端 return c.json(finalResult) }) app.post(`/callback_here`, async (c) =>{ // 从请求参数中获取第三方传回的requestId const { requestId } = c.request.query const callbackData = await c.request.json() // 取出对应的resolver const resolve = pendingRequests.get(requestId) if (resolve) { // 触发Promise,让等待的GET接口继续执行 resolve(callbackData) // 清理Map中的数据,避免内存泄漏 pendingRequests.delete(requestId) } // 给第三方返回成功响应 return c.json({ status: 'success' }) }) // 自定义的后续处理函数 function handleCallbackData(data) { // 这里编写回调数据的处理逻辑 return { processed: true, data } }
优缺点
- 优点:实现简单,无需额外依赖。
- 缺点:
- 内存存储,服务器重启后未处理的请求会丢失;
- 不适合大流量场景,Map会占用过多内存;
- 需额外添加超时处理,避免请求无限挂起,可通过
Promise.race结合setTimeout实现。
方案二:持久化队列方案(生产环境推荐)
如果是生产环境或大流量场景,建议用持久化消息队列(如Redis)实现,避免内存泄漏和服务器重启丢失请求的问题。
实现思路
/my_url生成唯一requestId,发送请求时将ID传递给第三方;- 用Redis的
BLPOP(阻塞式弹出)让接口等待对应ID的消息; /callback_here收到回调后,将数据存入Redis对应队列;BLPOP获取到数据后,完成后续处理并返回响应。
示例代码(基于Redis)
import redis from 'redis' // 初始化Redis客户端 const client = redis.createClient() await client.connect() app.get(`/my_url`, async (c) => { const requestId = crypto.randomUUID() const formData = new FormData() formData.append(`callback_url`, `https://my-url.com/callback_here?requestId=${requestId}`) await fetch(`external-url.com`, { method: `POST`, body: formData, }) // 阻塞等待Redis队列消息,超时时间设为5分钟(可按需调整) const [, callbackDataStr] = await client.blPop(`callback:${requestId}`, 300) if (!callbackDataStr) { return c.json({ error: '请求超时' }, { status: 408 }) } const callbackData = JSON.parse(callbackDataStr) const finalResult = handleCallbackData(callbackData) return c.json(finalResult) }) app.post(`/callback_here`, async (c) =>{ const { requestId } = c.request.query const callbackData = await c.request.json() // 将回调数据存入Redis队列 await client.rPush(`callback:${requestId}`, JSON.stringify(callbackData)) // 设置队列过期时间,避免Redis内存浪费 await client.expire(`callback:${requestId}`, 3600) return c.json({ status: 'success' }) })
优缺点
- 优点:
- 持久化存储,服务器重启后未处理请求不会丢失;
- 适合大流量场景,Redis性能足够支撑;
- 可灵活设置超时时间,避免请求无限等待。
- 缺点:需额外部署Redis服务,增加架构复杂度。
额外注意事项
- 必须给GET接口设置超时时间,避免前端和服务器长时间挂起连接;
- 第三方回调可能重复触发,需做幂等处理;
- 如果第三方不支持传递自定义参数,可将
requestId嵌入callback_url路径,如https://my-url.com/callback_here/${requestId}; - 生产环境要做好日志监控,记录每个请求的
requestId、回调状态等信息,方便排查问题。
内容的提问来源于stack exchange,提问作者cSharp
相关产品推荐
相关产品推荐

