需求背景:项目中下载数据的地方很多。有时候遇到上百万条数据,一口气返回,可能内存不够用。需求:有没有一种方法可以让我每次循环一点数据并返回?解决方案:目前我能想到的有两种——一种是node端使用stream方式返回,前端使用window.location.href方式打开后端接口。另一种浏览器端js-led导出动态数据是后端提供分页接口,前端使用StreamSaver.js(不限制文件大小)或FileSaver.js(文件大小受服务器可用内存限制)前端和Blob允许的最大值)即2G)保存文件。两种方式各有优势,根据自己的需要选择。解决方案的优点和缺点服务器端stream1。只发起一个http请求。2.前后端开发总量小,基本是后端的工作量。1.如果接口可能返回json让前端判断是否下载,前端会很吃力。2.如果运维不愿意延长网关超时时间,也是一个缺点。前端stream1。前端可以做更细致的判断。好的。个人还是更喜欢前端的Stream,因为它可以满足更多的异常需求,而且做了一次以后代码可以复用。但是这篇文章的题目是使用node+koa以流的形式返回数据,所以本文先介绍第一种,本文介绍另外一种:浏览器端js-led导出动态数据。服务端流参考koa文档,只需要ctx.body=右边的value类型为ReadableStream即可。然后就可以使用stream.Readable了,因为我不习惯stream.Readable本身的用法,所以封装了一个简单的函数:/***创建一个可读流,循环调用getData函数获取数据,当函数返回null最后,如果返回undefined,会被认为是返回空字符串*@paramgetDatasize参数是用来指一次返回多少数据,不是说应该严格跟着。大小单位应该是字节。必须返回的是utf8编码**/functioncreateReadableStream(getData:(size:number)=>Promise){returnnewstream.Readable({asyncread(size){while(true){letdata=nulltr??y{data=awaitgetData(size)}catch(e){console.error('[h-node-utils-errorcreateReadable]:',e)}constgoContinue=this.push(data,'utf8')if(!goContinue||data===null){break}}},})}使用方法:ctx.set('Content-Type','text/csv;charset=utf-8')//Chinese必须用encodeURIComponent包裹,否则Invalidcharacterinheadercontent["Content-Disposition"]ctx.set('Content-Disposition',`attachment;filename=${encodeURIComponent('detaileddata')}.csv`)let会报page=0ctx.body=createReadableStream(async()=>{page+=1//这里从数据库中读取一页数据,//如果有数据,将数据转成字符串返回,如果是csv就可以了,如果要用excel,需要检查是否有方法可以使用//如果没有更多的数据,则返回null})前端浏览器直接打开界面即可下载地址附:前端可以不跳转到空白页下载上面界面输出的方法://其实就是创建一个不可见的iframe,让这个iframe跳转到下载接口functiondownload(url){constiframe=document.createElement('iframe')iframe.style.display='none'functioniframeLoad(){console.log('iframeonload')constwin=iframe.contentWindowconstdoc=win.documentif(win.location.href===url){if(doc.body.childNodes.length>0){//响应错误}iframe.parentNode.removeChild(iframe)}}if('onload'iniframe){iframe.onload=iframeLoad}elseif(iframe.attachEvent){iframe.attachEvent('onload',iframeLoad)}else{iframe.onreadystatechange=functiononreadystatechange(){if(iframe.readyState==='complete'){iframeLoad()}}}iframe.src=''document.body.appendChild(iframe)setTimeout(functionloadUrl(){iframe.contentWindow.location.href=url},50)}