我们复现一下整个开发,与遇到的问题,及解决方式
概述
我们先看一下代码实现,然后逐步分析代码:
const XHRCallback = function () {
if (XHR.readyState == 4) {
if (XHR.status == 200) {
if (!~XHR.getResponseHeader('Content-type').indexOf('json')) {
//no exist json
let resBlob = XHR.response;
const _name = decodeURI(XHR.getResponseHeader('Content-Disposition').split('-')[1];
const url = e.target.result;
const a = document.createElement('a');
a.download = _name;
a.href = URL.createObjectURL(url);
a.click();
} else {
const reader = new FileReader();
reader.onload = function (e) {
const _transferData = e.target.result;
const _res = JSON.parse(_transferData);
//prompt _res.errmsg
};
reader.readAsText(XHR.response, 'UTF-8');
}
} else {
//prompt no-200 error
}
}
};
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = XHRCallback;
XHR.open('GET', 'manage/export', true);
XHR.responseType = 'blob';
XHR.send();
代码 p-1
Q1: 怎么设置返回值类型
当返回数据类型为application/vnd.ms-excel
时,我们需要将这种返回值作为二进制处理。
可以通过在xhr.send()
前,设置xhr.reponseType = 'blob'
即可。这时返回的数据即为blob
类型。
需要注意:responseType
设置成 blob
或 arraybuffer
后,responseText 、responseXML 就取不到了
Q2: responseType 是什么,怎么配置?
responseType
定义了响应数据的类型。
The
XMLHttpRequest
propertyresponseType
is an enumerated string value specifying the type of data contained in the response. It also lets the author change the response type. If an empty string is set as the value ofresponseType
, the default value of"text"
is used.
responseType
不能再loading和已经返回result
后配置,是没有用的,需要在send
之前配置。
返回的应该是流,你用let blob = new Blob([xhr.response], {type:'image/jpeg')来构造成Blob,然后图片的src用url.createObjectURL (blob)来读取,一般这样没问题了。如果未解决,ajax请求的图片后台返回的content-type未设置的话默认是image形式,你可以再设置xhr.responseType为'blob'或arraybuffer。确保数据是Blob。
作者:KennyWu
链接:https://www.zhihu.com/question/66477284/answer/242991929
Value | Description |
---|---|
"" |
An empty responseType string is treated the same as "text" , the default type (therefore, as a DOMString . |
"arraybuffer" |
The response is a JavaScript ArrayBuffer containing binary data. |
"blob" |
The response is a Blob object containing the binary data. |
"document" |
The response is an HTML Document or XML XMLDocument , as appropriate based on the MIME type of the received data. See HTML in XMLHttpRequest to learn more about using XHR to fetch HTML content. |
"json" |
The response is a JavaScript object created by parsing the contents of received data as JSON. |
"text" |
The response is text in a DOMString object. |
Q3: 用 FileReader 也可以处理 Blob 类型
const XHRCallback = function () {
if (XHR.status == 200) {
if (XHR.readyState == 4) {
const resBlob = XHR.response;
const reader = new FileReader();
reader.on = function (e) {
const url = e.target.result;
const a = document.createElement('a');
a.href = url;
a.click();
};
reader.readAsDataUrl(resBlob);
}
}
};
代码 3-1
但是用 FileReader.readAsDataUrl
会处理成 base64
编码 (形式如:data:application/vnd.ms-excel;base64,)对于比较小的文件,没有问题,但是对于比较大的文件,会出现文件编译后,字符串对于 href
长度过长的问题
下面是来自Google的两个回答:
Is there a way to get around the limit?
Very hardly.
It is even probable that the limitations vary from browser to browser, or from E-Mail client to E-Mail client.
I would rather use a HTML form and a server-side script to send the message.
Yes, there is a limit on the length of the URL.
The limit varies from browser to browser, so you should keep the URL below 2000 characters to be safe.
Internet Explorer seems to be the browser that is having the shortest limit. According to this article it’s 2083 characters.
对于 a
标签 href
属性的长度对于不同浏览器是有不同的限制的
Q4: 如何最优化处理来自 server 的不同数据类型
先来看一下 readyState
不同状态码对应的含义:
Value | State | Description |
---|---|---|
0 | UNSENT | Client has been created. open() not called yet. |
1 | OPENED | open() has been called. |
2 | HEADERS_RECEIVED | send() has been called, and headers and status are available. |
3 | LOADING | Downloading; responseText holds partial data. |
4 | DONE | The operation is complete. |
const XHRCallback = function () {
if (
XHR.readyState == 4 &&
XHR.status == 200
) {
const resBlob = XHR.response;
const _name = decodeURI(XHR.getResponseHeader('Content-Disposition').split('-')[1];
const reader = new FileReader();
reader.on = function (e) {
const url = e.target.result;
const a = document.createElement('a');
a.download = _name;
a.href = url;
a.click();
};
reader.readAsDataUrl(resBlob);
} else if (XHR.readyState == 2) {
if (!!~XHR.getResponseHeader('Content-type').indexOf('json')) {
//exist json
//has capture the head info
//prompt client the json message (这里只能提示用户前端自定义错误信息,而不能得到服务器端返回的错误信息)
XHR.abort();
}
//会在 readyState == 2 时候得到 status, 之前 status = 0
} else if (XHR.readyState == 2 && XHR.status !== 200) {
//prompt client the xhr failure
XHR.abort();
}
};
代码 4-1
上面的代码看似正确,但是有一个错误在里面,就是如果在 readyState == 2
中是获取不到服务器端返回的结果的,结果只有在 readyState == 4 && status == 200
中才能返回。这里虽然高效,但是只能返回给用户前端自定义的错误结果。
status
会在 readyState == 2
时候得到,在这之前 status == 0
所以在这里,我们选择了 代码 p-1 中的形式,因为 json
返回结果并不是很多,数据量不大的情况下选择第一种处理方式还是合理的,如果错误信息的数据量同样很大,那么在 readyState == 2
时中止处理也是合理的,需要根据实际情况选择处理方式。
Q5: XHR.abort() 是什么意思?它的工作原理是怎么样的?
先来看一段来自于 MDN 的原话:
The
XMLHttpRequest.abort()
method aborts the request if it has already been sent. When a request is aborted, itsreadyState
is changed toXMLHttpRequest.UNSENT
(0) and the request'sstatus
code is set to 0.
这段话的翻译是:在执行这个指令后,readyState
会被重置为 0
,status
被重置为 0
。
但是实际中发现,上面的代码 (4-1)中,直接将 readyState
和 status
设置成了 4
和 0
,然后执行一遍 onreadystatechange
的回调函数,然后 readySate
会被设置成 0
。
结论:在实际中(chrome),不论在何处调用 abort
(在 send
后直接调用,或在回调函数中调用),都会将 readyState
设置成 4
,并以 4
执行一遍回调函数,这是为什么呢?我们来做一下测试,看看
XMLHttpRequst
和 abort
到底是如何工作的。
fragment 1:
const XHRCallback = function () {
console.log(XHR.readyState);
};
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = XHRCallback;
XHR.open('GET', 'manage/export', true);
XHR.responseType = 'blob';
XHR.send();
console.log('open has executed!');
代码 5-1
输出结果:
1
open has executed!
2
3
4
结论:
可以看出
open
是同步的,立马执行回调函数,而send
是异步的
fragment 2:
const XHRCallback = function () {
console.log(XHR.readyState, XHR.status);
};
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = XHRCallback;
XHR.open('GET', 'manage/export', true);
XHR.responseType = 'blob';
XHR.send();
XHR.abort();
console.log('open has executed!');
代码 5-2
输出结果:
1 0
4 0
open has excuted!
结论:
abort
是同步操作,它会迅速组织后面的所有异步操作,并且将readyState
和status
设置成4
和0
,然后调用一下回调函数,再将readyState
设置成0
fragment 3:
const XHRCallback = function () {
console.log(XHR.readyState, XHR.status);
if (XHR.readyState == 2) {
XHR.abort();
console.log(XHR.readyState, XHR.status);
}
};
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = XHRCallback;
XHR.open('GET', 'manage/export', true);
XHR.responseType = 'blob';
XHR.send();
console.log('open has executed!');
代码 5-3
输出结果:
1 0
open has executed!
2 200
4 0
0 0
结论:
再一次验证了上述的几个结论
Q6: FileReader 的 readAsText 是什么?为什么不用 readAsBinaryString
MDN 对于 readAsBinaryString 的定义如下:
theresult
attribute contains the raw binary data from the file.
而对 readAsText 的定义则是:
and theresult
attribute contains the contents of the file as a text string.
instanceOfFileReader.readAsText(blob[, encoding]);
我们发现,readAsText
可以设置编码种类,当有中文的时候,readAsBinaryString
会出现乱码,而 readAsText
设置了 UTF-8
则不会。
Q7: FileReader 中的 onload 和 onloadend 的区别
可以参考上述文章
Q8: ajax 中怎么设置前端超时?
看如下一段代码
//终止请求和超时
function timeedGetText(url,timeout,callback){
var xhr = new XMLHttpRequest();
var timedout = false;
var timer = setTimeout(function(){
timedout = true;
xhr.abort();
},timeout);
xhr.onreadystatechange = function(){
if(xhr.readyState !=4){ return;}
if(timedout){ return;}
clearTimeout(timer);
if(xhr.status === 200){
callback(xhr.responseText);
}
};
}
Q9: 手动触发 dom 原生事件的方法
除了 click
这样的方法,或许我们还有别的方法
const save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
const ev = document.createEvent("MouseEvents");
ev.initMouseEvent(
"click", true, false, window, 0, 0, 0, 0, 0
, false, false, false, false, 0, null
);
save_link.dispatchEvent(ev);
网友评论