乐趣区

关于node.js:nodejskoa以流的形式返回数据

需要背景:我的项目中有多处下载数据的中央,有时候遇到几百万条数据,一口气返回的话,可能会导致内存不够用。

需要:是不是有一种办法,能让我循环每次取一点数据返回?

解决方案:目前想到两种——

一种是 node 端应用 stream 形式返回,前端用 window.location.href 的形式关上后端接口。

另一种是后端提供分页接口,前端应用 StreamSaver.js(文件大小无限度)或 FileSaver.js(文件大小受限于前前端可用内存和 Blob 容许的最大值即 2G)保留文件。

两种办法各有劣势,按需选取。

计划 长处 毛病
服务端 stream 只发动一次 http 申请 如果接口有可能会返回 json 让前端判断是否下载,则前端会很难。如果运维不违心加长网关超时,也是一个毛病
前端 stream 前端能够做更细的判断 发动屡次 http 申请

本文先介绍第一种,另一种另起一篇文章。

服务端 stream

查阅 koa 的文档,只须要 ctx.body= 左边的值类型是 ReadableStream 即可。那么能够用 stream.Readable,因为我不习惯 stream.Readable 自身的用法,所以我封装了一个繁难的函数:

/**
 * 创立一个可读 stream,循环调用 getData 函数获取数据,当 该函数 返回 null 时完结,如果返回 undefined,会认为是返回空字符串 * @param getData size 参数是用于参考单次返回多少数据,不是说要严格依照这个。size 单位应该是字节。必须返回的是 utf8 编码的 * */
 function createReadableStream(getData: (size: number) => Promise<string | null>
) {
  return new stream.Readable({async read(size) {while (true) {
        let data = null
        try {data = await getData(size)
        } catch (e) {console.error('[h-node-utils-error createReadable]:', e)
        }
        const goContinue = this.push(data, 'utf8')
        if (!goContinue || data === null) {break}
      }
    },
  })
}

应用办法:

ctx.set('Content-Type', 'text/csv; charset=utf-8')
// 中文必须用 encodeURIComponent 包裹,否则会报 Invalid character in header content ["Content-Disposition"]
ctx.set(
  'Content-Disposition',
  `attachment; filename=${encodeURIComponent('具体数据')}.csv`
)
let page = 0
ctx.body = createReadableStream(async () => {
  page += 1
  // 这里从数据库读一页数据,// 如果有数据,把数据转为字符串,如果是 csv 则够用了,如果要用 Excel,须要查查有没有办法能够用
  // 如果没有更多数据了,返回 null
})

前端浏览器间接关上该接口地址即可下载

退出移动版