Fastify提前返回响应,如何正确返回Cloudinary图片上传链接?
问题
我是Fastify和后端开发新手,首次尝试通过Fastify服务器将图片上传至Cloudinary。upload_stream方法接收两个参数:第一个是配置对象,第二个是图片上传完成后执行的回调函数。
我的代码如下:
async function uploadImage(request: FastifyRequest, reply: FastifyReply) { const pipeline = util.promisify(stream.pipeline); const data = await request.file(); if(!data) { return reply.code(400).send("no data") } const randomFilename = crypto.randomBytes(30).toString('hex'); await pipeline( data.file, server.cloudinary.uploader.upload_stream( { public_id: randomFilename, folder: 'library' }, (error, result) => { if(result) { console.log(result); reply.code(201).send({ meta: { code: 201, message: result?.url } }); } else { reply.code(400).send({ meta: { code: 400, message: error } }); } }) ); }
图片已成功上传至Cloudinary,回调中的console.log(result)也能在控制台打印结果对象,但尝试将图片链接返回给响应时,出现“Reply was already sent”错误,提示响应已以200状态码发送(代码中并未设置200状态码)。完整错误信息如下:
WARN (17040): Reply already sent reqId: "req-1" err: { "type": "FastifyError", "message": "Reply was already sent.", "stack": FastifyError: Reply was already sent. at _Reply.Reply.send (C:\...\fastify-test\node_modules\fastify\lib\reply.js:127:26) at C:\...\fastify-test\src\api\user\controller.ts:133:29 at C:\...\fastify-test\node_modules\cloudinary\lib\utils\index.js:1275:12 at IncomingMessage.<anonymous> (C:\...\fastify-test\node_modules\cloudinary\lib\uploader.js:509:9) at IncomingMessage.emit (node:events:525:35) at IncomingMessage.emit (node:domain:489:12) at endReadableNT (node:internal/streams/readable:1359:12) at processTicksAndRejections (node:internal/process/task_queues:82:21) "name": "FastifyError", "code": "FST_ERR_REP_ALREADY_SENT", "statusCode": 500 }
请问如何正确返回已上传图片的链接作为服务器响应?
解决方案
问题出在你同时使用了util.promisify(stream.pipeline)和Cloudinary的回调函数。当await pipeline(...)执行完成时,Fastify会自动发送一个200状态码的空响应,而之后Cloudinary的回调才触发,此时再调用reply.send()就会报错“响应已发送”。
正确的做法是把upload_stream包装成Promise,通过Promise的resolve/reject来处理上传结果,避免回调和promisified pipeline的异步冲突。修改后的代码如下:
async function uploadImage(request: FastifyRequest, reply: FastifyReply) { const data = await request.file(); if (!data) { return reply.code(400).send({ meta: { code: 400, message: "no data" } }); } const randomFilename = crypto.randomBytes(30).toString('hex'); // 将upload_stream包装为Promise const uploadResult = await new Promise((resolve, reject) => { data.file.pipe(server.cloudinary.uploader.upload_stream( { public_id: randomFilename, folder: 'library' }, (error, result) => { if (error) { reject(error); } else { resolve(result); } } )); }); // 上传成功后返回响应 return reply.code(201).send({ meta: { code: 201, message: (uploadResult as any)?.url } }); }
关键说明
- 不再使用promisified的
pipeline,直接用data.file.pipe()将文件流导入Cloudinary的上传流,这样不会触发Fastify自动发送响应。 - 用Promise包装
upload_stream的回调,通过await等待上传完成,之后再统一发送响应,避免重复发送的问题。 - 可以添加
try/catch来捕获上传失败的情况,返回对应的错误响应:
async function uploadImage(request: FastifyRequest, reply: FastifyReply) { try { const data = await request.file(); if (!data) { return reply.code(400).send({ meta: { code: 400, message: "no data" } }); } const randomFilename = crypto.randomBytes(30).toString('hex'); const uploadResult = await new Promise((resolve, reject) => { data.file.pipe(server.cloudinary.uploader.upload_stream( { public_id: randomFilename, folder: 'library' }, (error, result) => { if (error) { reject(error); } else { resolve(result); } } )); }); return reply.code(201).send({ meta: { code: 201, message: (uploadResult as any)?.url } }); } catch (error) { return reply.code(400).send({ meta: { code: 400, message: error instanceof Error ? error.message : String(error) } }); } }
内容的提问来源于stack exchange,提问作者asus18019
相关产品推荐
相关产品推荐

