如何在formidable文件上传请求中等待writeHandler的执行结果?
问题描述
使用formidable将文件直接上传至AWS S3时,按照官方示例能正常运行,但需要等待S3上传完成后,在HTTP响应中返回文件的最终URL。当前form.parse会立即返回,无法等待S3上传完成;尝试用Promise/await改造uploadStream时,因类型要求必须返回Writable流而非Promise,导致报错:
Type '(file: any) => Promise<any>' is not assignable to type '(file?: VolatileFile | undefined) => Writable'.
现有核心代码:
const s3Client = new AWS.S3({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_KEY, }, }); const uploadStream = (file) => { const pass = new PassThrough(); s3Client.upload( { Bucket: 'demo-bucket', Key: file.newFilename, Body: pass, }, (err, data) => { console.log(err, data); }, ); return pass; }; const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { const form = formidable({ fileWriteStreamHandler: uploadStream, }); form.parse(req, () => { res.writeHead(200); res.end(); }); return; } });
期望实现:在S3上传完成后,返回文件URL:
form.parse(req, () => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ url: 'https://s3.amazonaws.com/my-file' }, null, 2)); });
解决方案
方法一:收集上传Promise,等待全部完成后响应
核心思路是在uploadStream内部为每个文件创建上传Promise,收集所有Promise后,在form.parse的回调中等待全部完成,再提取URL返回。
改造后代码:
const { PassThrough } = require('stream'); const AWS = require('aws-sdk'); const http = require('http'); const formidable = require('formidable'); const s3Client = new AWS.S3({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_KEY, }, }); // 存储所有文件的上传Promise let uploadPromises = []; const uploadStream = (file) => { const pass = new PassThrough(); // 创建S3上传Promise const uploadPromise = new Promise((resolve, reject) => { s3Client.upload( { Bucket: 'demo-bucket', Key: file.newFilename, Body: pass, }, (err, data) => { if (err) reject(err); else resolve(data); }, ); }); // 将Promise加入数组 uploadPromises.push(uploadPromise); return pass; }; const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { uploadPromises = []; // 重置Promise数组,避免跨请求污染 const form = formidable({ fileWriteStreamHandler: uploadStream, }); form.parse(req, async (err, fields, files) => { if (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); return; } try { // 等待所有文件上传到S3完成 const uploadResults = await Promise.all(uploadPromises); // 提取第一个文件的URL(多文件可遍历处理) const fileUrl = uploadResults[0].Location; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ url: fileUrl }, null, 2)); } catch (uploadErr) { res.writeHead(500); res.end(JSON.stringify({ error: uploadErr.message })); } }); return; } // 处理其他请求 res.writeHead(404); res.end('Not Found'); });
方法二:监听formidable的file事件处理上传
不使用fileWriteStreamHandler,而是直接监听form的file事件,在事件回调中处理单个文件的流上传,并收集Promise,最后在end事件中等待全部完成。
代码示例:
const { PassThrough } = require('stream'); const AWS = require('aws-sdk'); const http = require('http'); const formidable = require('formidable'); const s3Client = new AWS.S3({ credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_KEY, }, }); const server = http.createServer((req, res) => { if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') { const uploadPromises = []; const form = formidable(); // 监听每个文件的上传事件 form.on('file', (name, file) => { const pass = new PassThrough(); // 创建上传Promise const uploadPromise = new Promise((resolve, reject) => { s3Client.upload( { Bucket: 'demo-bucket', Key: file.newFilename, Body: pass, }, (err, data) => { if (err) reject(err); else resolve(data); }, ); }); uploadPromises.push(uploadPromise); // 将文件流导向PassThrough,传给S3 file.stream.pipe(pass); }); // 所有文件解析完成后,等待S3上传完成 form.on('end', async () => { try { const uploadResults = await Promise.all(uploadPromises); const fileUrl = uploadResults[0].Location; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ url: fileUrl }, null, 2)); } catch (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); } }); // 处理解析错误 form.on('error', (err) => { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); }); form.parse(req); return; } res.writeHead(404); res.end('Not Found'); });
关键说明
fileWriteStreamHandler强制要求返回Writable流,因此不能直接返回Promise,但可以在函数内部创建Promise并收集- 使用
Promise.all等待所有文件的S3上传完成,S3的upload回调返回的data.Location就是文件的公开URL - 必须处理错误场景:包括form解析错误、S3上传错误,避免请求挂起或无响应
内容的提问来源于stack exchange,提问作者itinance
相关产品推荐
相关产品推荐

