进阶的ajax——ajax实现临时文件下载

2,585 阅读3分钟

写在本文之前,想一想我们常用的文件下载是怎样做的?

通过a标签

    <a target="_blank" href="//www.xxx.com/download/file.txt" download="file">下载</a>

通过form表单

    <form action='/download' method='post'>
        <input type="text" name="name" value="file"/>
        <button>下载</button>
    </form>

这些都没有问题,包括使用条件筛选然后下载。但是如果需求是要在筛选前做条件验证呢? 相信这也难道不倒聪明的你。

我曾经写过一个相对“优雅”的a标签根据条件下载并作出反馈😏:


class DownloadPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            name: ''
        };
    }

    onChange = (e) => {
        const name = e.target.value;
        this.setState({
            name
        });
    }

    onDownload = (e) => {
        if (!this.state.name) {
            window.alert('请先输入筛选条件');
            e.preventDefault();
        }
    }

    render() {
        const { name } = this.state;
        return (
            <div>
                <input value={name} onChange={(e) => this.onChange(e)} />
                <a target="_blank" href={`//www.xxx.com/download?name=${name}`} onClick={() => this.onDownload()}>导出文件</a>
            </div>
        );
    }
}

以上代码是react的例子,但是任何框架的实现都是如此,思路不会被框架所限制。


上文中的方式绝对能够满足绝大多数的业务需求了,但是这不符合本文的目的,我们今天要讲的是怎样通过ajax下载文件。

ajax下载文件

通常来说,ajax一般用来做数据交换。

想想吧,如果把上面例子中的条件放到另一个接口中验证。。。等到接口返回再e.preventDefault(),事件早已触发默认行为并作出响应了。

ajax下载实现基础: ajax(responseType)、createObjectURL、Blob

ajax(responseType)

responseType顾名思义,是用来设置ajax返回数据的类型,允许的值有arraybuffer、blob、document、json、text,默认值为text,值得注意的是xhr.responseType设置必须在open之后,send之前调用。我们在这里需要设置:

xhr.responseType = 'arraybuffer'

这表明ajax将会接受一个arraybuffer类型的数据。

Blob

Blob对象是一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。例如我们可以设置一个excel文件:

new Blob(bufferData, {type: 'application/vnd.ms-excel'})

navigator.msSaveBlob

msSaveBlob方法可以将js blob保存到电脑上,从而实现下载效果。

navigator.msSaveBlob(blob, fileName);

createObjectURL、revokeObjectURL

createObjectURL的作用是创建一个新的对象URL,该对象URL可以代表某一个指定的file对象或Blob对像,也就是在内存中存了一个可访问的文件。需要注意的是文件存储在内存中占用过大,所以一定要记得revokeObjectURL释放内存。 使用代码如下:

const objectURL = window.URL.createObjectURL(blob);
...
window.URL.revokeObjectURL(link.href)

具体实现

那么我们就很明白了,后端返回一个buffer,前端设置接收类型为arraybuffer,{ responseType: 'arraybuffer' },前端接收到到buffer对象。

通过buffer.data取到buffer值,通过blob设置type为文件格式。例如: new Blob([res.data], {type: 'application/vnd.ms-excel'}) ,就是将文件设置为excel文件格式。

最后使用navigator.msSaveBlob保存为本地文件。

如果你的浏览器不支持msSaveBlob,你也可以通过 a 标签,设置href指向createObjectURL文件 window.URL.createObjectURL(blob),调用click导出。最后revokeObjectURL释放文件内存。

示例

  • 后端代码 (node)
    const express = require('express')
    const app = express()
    app.post('*', (req, res) => {
        res.send(new Buffer('this is a test demo'))
    })
  • 前端代码
    const axios = require('axios')
    let Service = axios.create({
        timeout: 60 * 1000,
        baseURL: 'http://www.xxx.com',
        headers: {
            'Content-Type': 'application/json;charset=UTF-8'
        }
    })
    Service.post('/xxx/xxx/importExcel', param, { responseType: 'arraybuffer' }).then((res) => {
        const fileName = '测试文件'
        const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'})
        if (window.navigator.msSaveOrOpenBlob) {
            navigator.msSaveBlob(blob, fileName)
        } else {
            var link = document.createElement('a')
            link.href = window.URL.createObjectURL(blob)
            link.download = fileName
            link.click()
            window.URL.revokeObjectURL(link.href)
        }
    })

通过这几行代码,我们就能直接接收后端接口返回的文件流了。

用这种方法实现下载的好处是不限制请求类型,也能保持相对干净的dom结(只需要一个按钮,剩下的都交给js去做吧!)。

结束语

当然本文所讲述到的api并不仅仅限于下载ajax文件,通过讲解的过程我们是不是学到了更多的api,前端操作文件真的是大有可玩。

比如说我们可以实现一个前端生成文件的工具,只需要使用blob + msSaveBlob/createObjectURL就可以轻松做到。

const blob = new Blob(['这是一个前端文件'], {type: 'application/vnd.ms-excel'})
const fileName = '测试文件'
if (window.navigator.msSaveOrOpenBlob) {
    navigator.msSaveBlob(blob, fileName)
} else {
    var link = document.createElement('a')
    link.href = window.URL.createObjectURL(blob)
    link.download = fileName
    link.click()
    window.URL.revokeObjectURL(link.href)
}

怎么样,学到了吗?

--The End