关于前端form提交后端返回文件流触发浏览器下载(并发控制)

前文介绍过这种文件下载方式,它有好处也有不足的地方。好处是可以充分利用浏览器自身的资源调度优势,开发只管提交不用关注下载细节,用户可以在浏览器下载任务中看到下载的状态。不足之处是只能下载到浏览器配置的默认下载位置,而且JS里不能掌握下载状态。

更为关键的是如果一次下载的文件较多的话,提交了N个请求,这样浏览器会创建N个标签页,虽然浏览器自己会调度,下载完后自行关闭这些标签页,但是会带来密集恐惧,那有没有办法控制下浏览器下载的任务数(虽然处于下载中的最多6个任务,但是等待的任务也是任务啊)

既然文件下载提交后,无法从浏览器得到任务下载状态,那么从服务器呢,这个方案是可行的。

前端任务提交后,后端服务器读取本地文件流通过pipeline对接到response,response设置头参数,pipeline完成后回调sse消息发送到前端通知文件下载完成。

javascript 复制代码
app.get('/events', (req,res)=>{
	res.writeHead(200,  { 'Content-Type': 'text/event-stream', 'Connection': 'keep-alive', 'Cache-Control': 'no-cache' });
	res.write(`data: ${JSON.stringify({"msg":"消息服务正常"})}\n\n`);
	let clientid=stringRandom(32, { letters: 'ABCDEF' });
	clients.push({ "clientid":clientid, "uid":req.session.user.userid, "res":res });
	req.on('close', () => { clients = clients.filter(item => (item.clientid != clientid)); });
});

function sendssemsg(jvar)
	clients.forEach(client=>{ if (client.uid==jvar.uid) { client.res.write(`data: ${JSON.stringify(jvar.msg)}\n\n`) } });
}

...
app.post("/getfile",express.urlencoded({ extended: false }),(req,res)=>{
...
	res.set({ "Content-Type": "application/octet-stream",  "Content-Disposition": "attachment;filename* = UTF-8''"+fixedEncodeURIComponent(filename.substr(filename.lastIndexOf("/")+1)),"Content-Length": stats.size });
	pipeline(fs.createReadStream(filepath), res, (err) => { 
		if (err) console.log("下载出错") 
		else sendssemsg({"uid":userid,"msg":filename+" 下载完成"})
		});
	...

通过sse发送下载结果,效率最好

前端收到相应的sse消息,就可以处理下载任务队列操作了

javascript 复制代码
	function downloadfile(filename) {
		let form=$("#formp");
		$("input[name=filename]").attr("value",filename);
		form.submit();
		}
	
	var indownloading=0;
	const evtSource = new EventSource("/events");

	evtSource.addEventListener('message', function(event) {
		//let jvar=JSON.parse(event.data);
		indownloading-=1;
		if (waitinglist.length>0) {
			indownloading+=1;
			downloadfile(waitinglist.shift());
			}
		})

	let indownloading=0;
	let taskLimit=navigator.hardwareConcurrency;
	while ((indownloading<tasklimit)&&(waitinglist.length>0)) {
		indownloading+=1;
		downloadfile(waitinglist.shift());
		}
html 复制代码
<form name="formp" id="formp" action="/getfile" method="post" target="_blank" rel="noopener noreferrer">
<input name="filename" value="">
</form>

这样效果就是下载任务控制在CPU核数,浏览器上看到的下载标签也不会太多,视觉观感上对用户更友好一些。

相关推荐
尾善爱看海2 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
GreenTea2 小时前
7000 万 QPS、500 PB:OpenAI 如何用一个 Python 存储平台撑住 10 亿用户
后端·架构
一技安身2 小时前
【信创】 OpenWebUI v0.10.2 / v0.11.2 新建知识库莫名消失,刷新只显示数字-不见知识库列表解决办法
chrome
Flynt2 小时前
Java 27 升级实测:默认值动得比新特性多,有个老参数会让 JVM 直接起不来
java·jvm·后端
Bs_MoneyMagnet2 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
驳是2 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea2 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持3 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者3 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn3 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端