【JS】浏览器端js主导的导出动态数据

浏览器js主导的导出动态数据

黒之染发布于 今天 06:59

当一个系统需要导出动态数据时,有时候首选方案是:由服务端实时生成csv或Excel格式的文件,然后用二进制流的形式返回给前端。

比如这里说的方法: nodejs+koa以流的形式返回数据

这时候会遇到一些问题。其中最大的问题是,如果数据量很大,处理时长,超过了网关设置的超时时间怎么办?

这时候不得不寻找其他的下载/导出方案。其实解决方案很多,这里推荐一个比较全能的方案,而且实现起来不复杂。

StreamSaver.js 可以解决问题,而且它对文件大小,没有限制。

FileSaver.js 也能做到,但它文件大小受限于前端可用内存和Blob允许的最大值即2G

在这个方案里,服务端只需要提供一个分页接口,前端循环调用该接口拿数据,解析后写入同一个文件,甚至可以压缩成zip格式。

优点

前端可以给请求加上任意参数,也可以对接口返回的数据做解析判断,还可以中断下载。复杂点的话,也能做到断点续传。

缺点

多次请求会增加网关的负载。不过考虑到下载功能不是频繁使用,在大多数情况下还是可行的。分页合理的话,能最大限度降低多次http请求的性能损耗。

使用streamsaver还是挺自由的,下面是利用它封装了一个简易使用方法,可以满足一般的需求。更多使用方法,参考官方文档:https://github.com/jimmywarti...

函数声明:

/**

* 下载大文件

* 文档: https://github.com/jimmywarting/StreamSaver.js

* */

import streamSaver from 'streamsaver'

const encode = TextEncoder.prototype.encode.bind(new TextEncoder())

/**

* 注意,该方法可能会延迟七八秒后才调起浏览器的下载弹窗,所以用的时候记得做些loading提示

*

* @param fileName

* @param getData 该函数返回字符串,则会在之前返回的字符串最后加上这字符串。支持 \n 换行符。返回null或抛出错误时,才会完成下载。

* @param opt

* @param opt.onComplete 当下载完成后会执行,如果 getData 抛出错误,会把错误从这里返回。如果是用户手动取消, error === 'USER_CANCEL'。如果是下载成功,error===undefined

* @return 返回的对象里有些功能,有用的是 .abort() 函数,可以主动取消下载,用户下到一半的文件直接没了

* */

export default (

fileName: string,

getData: () => Promise<string | null>,

opt?: {

onComplete?: (error?: any | 'USER_CANCEL') => void

addBOM?: boolean

},

) => {

const fileStream = streamSaver.createWriteStream(fileName)

const writer = fileStream.getWriter()

if (opt?.addBOM !== false) {

// 文本文件如果没有 BOM 头,用微软的软件打开有时候会乱码,通常这个方法是用来下载csv的,所以加上比较好

writer.write(encode('\uFEFF')).then()

}

go(writer, getData, opt?.onComplete).then()

return writer

}

async function go(

writer: WritableStreamDefaultWriter,

getData: () => Promise<string | null>,

onComplete?: (error?: any | 'USER_CANCEL') => void

) {

let data = null

let getError

try {

data = await getData()

} catch (e) {

getError = e

console.error('[downloadLargeFile error]', e)

}

if (data === null) {

await writer.close()

onComplete?.(getError)

return

}

let hadResolve = false

if (onComplete) {

// 延时检查能否写入成功,不知道延时够不够

setTimeout(() => {

if (!hadResolve) {

writer.abort().then()

onComplete?.('USER_CANCEL')

}

}, 2000)

}

await writer.write(encode(data))

// 如果用户取消下载,上一行不会 resolve,所以下面就不会执行了

hadResolve = true

go(writer, getData, onComplete).then()

}

使用方法:

const wait = (msec: number) => new Promise((resolve) => setTimeout(resolve, msec))

let i = 0

downloadLargeFile(

`下载测试.txt`,

async () => {

// 这个函数内部可以做任何异步操作

if (i >= 20) {

return null

}

i += 1

await wait(1000)

console.log(111, i)

return `${i.toString()}\n`

},

{

onComplete: (error) => {

console.log('完成', error)

},

},

)

以上示例下载的txt文件效果如下:
【JS】浏览器端js主导的导出动态数据

javascript前端stream

阅读 247更新于 今天 07:06

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


黒之染

几年半个人练习生,喜欢ctrl c、ctrl v、delete

avatar

黒之染

两年半个人练习生,喜欢ctrl+c/ctrl+v/delete

3k 声望

32 粉丝

0 条评论

得票时间

avatar

黒之染

两年半个人练习生,喜欢ctrl+c/ctrl+v/delete

3k 声望

32 粉丝

宣传栏

当一个系统需要导出动态数据时,有时候首选方案是:由服务端实时生成csv或Excel格式的文件,然后用二进制流的形式返回给前端。

比如这里说的方法: nodejs+koa以流的形式返回数据

这时候会遇到一些问题。其中最大的问题是,如果数据量很大,处理时长,超过了网关设置的超时时间怎么办?

这时候不得不寻找其他的下载/导出方案。其实解决方案很多,这里推荐一个比较全能的方案,而且实现起来不复杂。

StreamSaver.js 可以解决问题,而且它对文件大小,没有限制。

FileSaver.js 也能做到,但它文件大小受限于前端可用内存和Blob允许的最大值即2G

在这个方案里,服务端只需要提供一个分页接口,前端循环调用该接口拿数据,解析后写入同一个文件,甚至可以压缩成zip格式。

优点

前端可以给请求加上任意参数,也可以对接口返回的数据做解析判断,还可以中断下载。复杂点的话,也能做到断点续传。

缺点

多次请求会增加网关的负载。不过考虑到下载功能不是频繁使用,在大多数情况下还是可行的。分页合理的话,能最大限度降低多次http请求的性能损耗。

使用streamsaver还是挺自由的,下面是利用它封装了一个简易使用方法,可以满足一般的需求。更多使用方法,参考官方文档:https://github.com/jimmywarti...

函数声明:

/**

* 下载大文件

* 文档: https://github.com/jimmywarting/StreamSaver.js

* */

import streamSaver from 'streamsaver'

const encode = TextEncoder.prototype.encode.bind(new TextEncoder())

/**

* 注意,该方法可能会延迟七八秒后才调起浏览器的下载弹窗,所以用的时候记得做些loading提示

*

* @param fileName

* @param getData 该函数返回字符串,则会在之前返回的字符串最后加上这字符串。支持 \n 换行符。返回null或抛出错误时,才会完成下载。

* @param opt

* @param opt.onComplete 当下载完成后会执行,如果 getData 抛出错误,会把错误从这里返回。如果是用户手动取消, error === 'USER_CANCEL'。如果是下载成功,error===undefined

* @return 返回的对象里有些功能,有用的是 .abort() 函数,可以主动取消下载,用户下到一半的文件直接没了

* */

export default (

fileName: string,

getData: () => Promise<string | null>,

opt?: {

onComplete?: (error?: any | 'USER_CANCEL') => void

addBOM?: boolean

},

) => {

const fileStream = streamSaver.createWriteStream(fileName)

const writer = fileStream.getWriter()

if (opt?.addBOM !== false) {

// 文本文件如果没有 BOM 头,用微软的软件打开有时候会乱码,通常这个方法是用来下载csv的,所以加上比较好

writer.write(encode('\uFEFF')).then()

}

go(writer, getData, opt?.onComplete).then()

return writer

}

async function go(

writer: WritableStreamDefaultWriter,

getData: () => Promise<string | null>,

onComplete?: (error?: any | 'USER_CANCEL') => void

) {

let data = null

let getError

try {

data = await getData()

} catch (e) {

getError = e

console.error('[downloadLargeFile error]', e)

}

if (data === null) {

await writer.close()

onComplete?.(getError)

return

}

let hadResolve = false

if (onComplete) {

// 延时检查能否写入成功,不知道延时够不够

setTimeout(() => {

if (!hadResolve) {

writer.abort().then()

onComplete?.('USER_CANCEL')

}

}, 2000)

}

await writer.write(encode(data))

// 如果用户取消下载,上一行不会 resolve,所以下面就不会执行了

hadResolve = true

go(writer, getData, onComplete).then()

}

使用方法:

const wait = (msec: number) => new Promise((resolve) => setTimeout(resolve, msec))

let i = 0

downloadLargeFile(

`下载测试.txt`,

async () => {

// 这个函数内部可以做任何异步操作

if (i >= 20) {

return null

}

i += 1

await wait(1000)

console.log(111, i)

return `${i.toString()}\n`

},

{

onComplete: (error) => {

console.log('完成', error)

},

},

)

以上示例下载的txt文件效果如下:
【JS】浏览器端js主导的导出动态数据

以上是 【JS】浏览器端js主导的导出动态数据 的全部内容, 来源链接: utcz.com/a/113889.html

回到顶部