如何实现JavaScript串行HTTP请求与文件写入,避免压垮小型服务器?
解决串行HTTP下载文件的问题
问题根源
你之前的代码之所以并发过高,核心原因是:
forEach循环会一次性触发所有异步任务,哪怕回调里用了async/await,也只会阻塞单个回调内部,不会阻止forEach继续遍历下一个元素,导致所有请求同时发起。- 直接用
setTimeout时,三个定时器会被同时注册,1秒后同时执行,所以总耗时只有1秒。
先修正你的实验代码(实现每秒生成一个文件)
把 forEach 换成 for...of 循环,它会等待每个 await 完成后再进入下一次迭代:
const fs = require('fs'); function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function run(arr) { for (const str of arr) { await sleep(1000); fs.writeFileSync(`junk/${str}.txt`, "lorem"); console.log(`已生成文件: ${str}.txt`); } } // 调用示例 run(["one", "two", "three"]);
核心解决方案:串行HTTP下载文件
将HTTP请求和文件写入的异步操作封装成Promise,再用 for...of 逐个执行,确保上一个文件下载完成后再发起下一个请求:
1. 封装异步下载函数
用 stream.pipeline(已Promise化)处理流操作,比手动监听 finish 更可靠,自动处理错误和资源关闭:
const http = require('http'); const fs = require('fs'); const { promisify } = require('util'); const pipeline = promisify(require('stream').pipeline); async function downloadFile(link) { const name = link.split("/")[5]; return new Promise((resolve, reject) => { const request = http.get(link, async (res) => { try { // 用pipeline处理流的传输和写入 await pipeline(res, fs.createWriteStream(name)); resolve(); } catch (err) { reject(err); } }); // 捕获请求本身的错误 request.on('error', reject); }); }
2. 串行执行所有下载任务
async function downloadSequentially(links) { for (const link of links) { try { console.log(`开始下载: ${link}`); await downloadFile(link); console.log(`下载完成: ${link}`); } catch (err) { console.error(`下载失败: ${link}`, err); // 可选:遇到错误是否继续下载后续文件,这里选择继续 continue; } } } // 调用你的链接数组 downloadSequentially(links);
额外优化(可选)
如果不想完全串行,还可以控制并发数(比如同时下载3个),避免压垮服务器的同时提升效率,但你的需求是完全串行,上面的代码已经满足。
内容的提问来源于stack exchange,提问作者icicleking
相关产品推荐
相关产品推荐

