Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ajax(05) #36

Open
cy0707 opened this issue Jan 3, 2017 · 0 comments
Open

Ajax(05) #36

cy0707 opened this issue Jan 3, 2017 · 0 comments
Labels

Comments

@cy0707
Copy link
Owner

cy0707 commented Jan 3, 2017

前言

最近在看HTML5的拖拽上传,看了Firefox的文档,跟着做了一个demo,其中涉及相关方向的知识,发现有些知识以前没有涉及到,特上网搜索相关知识后,补充一下。

如何获取response header

xhr提供了2个用来获取响应头部的方法:getAllResponseHeaders和getResponseHeader。
前者是获取 response 中的所有header 字段,后者只是获取某个指定 header 字段的值。另外,getResponseHeader(header)的header参数不区分大小写。

这两个方法有如下可能出现的问题?

  • 使用getAllResponseHeaders()看到的所有response header与实际在控制台 Network 中看到的 response header 不一样
  • 使用getResponseHeader()获取某个 header 的值时,浏览器抛错Refused to get unsafe header "XXX"

原因1:W3C的 xhr 标准中做了限制,规定客户端无法获取 response 中的 Set-Cookie、Set-Cookie2这2个字段,无论是同域还是跨域请求;

原因2:W3C 的 cors 标准对于跨域请求也做了限制,规定对于跨域请求,客户端允许获取的response header字段只限于“simple response header”和“Access-Control-Expose-Headers” (两个名词的解释见下方)。

"simple response header"

"simple response header"包括的 header 字段有:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma;

"Access-Control-Expose-Headers"

"Access-Control-Expose-Headers":首先得注意是"Access-Control-Expose-Headers"进行跨域请求时响应头部中的一个字段,对于同域请求,响应头部是没有这个字段的。这个字段中列举的 header 字段就是服务器允许暴露给客户端访问的字段。

所以getAllResponseHeaders()只能拿到限制以外(即被视为safe)的header字段,而不是全部字段;而调用getResponseHeader(header)方法时,header参数必须是限制以外的header字段,否则调用就会报Refused to get unsafe header的错误。

如何指定xhr.response的数据类型

有些时候我们希望xhr.response返回的就是我们想要的数据类型。比如:响应返回的数据是纯JSON字符串,但我们期望最终通过xhr.response拿到的直接就是一个 js 对象,我们该怎么实现呢?

有2种方法可以实现,一个是level 1就提供的overrideMimeType()方法,另一个是level 2才提供的xhr.responseType属性。

xhr.overrideMimeType()

overrideMimeType是xhr level 1就有的方法,所以浏览器兼容性良好。这个方法的作用就是用来重写response的content-type,这样做有什么意义呢?

比如:server 端给客户端返回了一份document或者是 xml文档,我们希望最终通过xhr.response拿到的就是一个DOM对象,那么就可以用xhr.overrideMimeType('text/xml; charset = utf-8')来实现。

再举一个使用场景,我们都知道xhr level 1不支持直接传输blob二进制数据,那如果真要传输 blob 该怎么办呢?当时就是利用overrideMimeType方法来解决这个问题的。

xhr.responseType

responseType是xhr level 2新增的属性,用来指定xhr.response的数据类型,目前还存在些兼容性问题。那么responseType可以设置为哪些格式呢,如下:

xhr.response 数据类型 说明
"" String字符串 默认值(在不设置responseType时)
"text" String字符串
"document" Document对象 希望返回 XML 格式数据时使用
"json" javascript 对象 存在兼容性问题,IE10/IE11不支持
"blob" Blob对象
"arrayBuffer" ArrayBuffer对象

虽然在xhr level 2中,2者是共同存在的。但其实不难发现,xhr.responseType就是用来取代xhr.overrideMimeType()的,xhr.responseType功能强大的多,xhr.overrideMimeType()能做到的xhr.responseType都能做到。所以我们现在完全可以摒弃使用xhr.overrideMimeType()了。

如何获取上传、下载的进度

在上传或者下载比较大的文件时,实时显示当前的上传、下载进度是很普遍的产品需求。
我们可以通过onprogress事件来实时显示进度,默认情况下这个事件每50ms触发一次。需要注意的是,上传过程和下载过程触发的是不同对象的onprogress事件:

  • 上传触发的是xhr.upload对象的 onprogress事件
  • 下载触发的是xhr对象的onprogress事件
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
//进度是否可用的布尔值
    if (event.lengthComputable) {
//loaded为上传或下载的大小/total为全部的大小
      var completedPercent = event.loaded / event.total;
    }
 }

xhr相关事件

interface XMLHttpRequestEventTarget : EventTarget {
  // event handlers
  attribute EventHandler onloadstart;
  attribute EventHandler onprogress;
  attribute EventHandler onabort;
  attribute EventHandler onerror;
  attribute EventHandler onload;
  attribute EventHandler ontimeout;
  attribute EventHandler onloadend;
};

interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {

};

interface XMLHttpRequest : XMLHttpRequestEventTarget {
  // event handler
  attribute EventHandler onreadystatechange;
  readonly attribute XMLHttpRequestUpload upload;
};
  • XMLHttpRequestEventTarget接口定义了7个事件:

    • onloadstart
    • onprogress
    • onabort
    • ontimeout
    • onerror
    • onload
    • onloadend
  • 每一个XMLHttpRequest里面都有一个upload属性,而upload是一个XMLHttpRequestUpload对象

  • XMLHttpRequest和XMLHttpRequestUpload都继承了同一个XMLHttpRequestEventTarget接口,所以xhr和xhr.upload都有第一条列举的7个事件

  • onreadystatechange是XMLHttpRequest独有的事件

所以这么一看就很清晰了:
xhr一共有8个相关事件:7个XMLHttpRequestEventTarget事件+1个独有的onreadystatechange事件;而xhr.upload只有7个XMLHttpRequestEventTarget事件。

事件触发顺序

当请求一切正常时,相关的事件触发顺序如下:

触发xhr.onreadystatechange(之后每次readyState变化时,都会触发一次)
触发xhr.onloadstart
//上传阶段开始:
触发xhr.upload.onloadstart
触发xhr.upload.onprogress
触发xhr.upload.onload
触发xhr.upload.onloadend
//上传结束,下载阶段开始:
触发xhr.onprogress
触发xhr.onload
触发xhr.onloadend
发生abort/timeout/error异常的处理

在请求的过程中,有可能发生 abort/timeout/error这3种异常。那么一旦发生这些异常,xhr后续会进行哪些处理呢?后续处理如下:

一旦发生abort或timeout或error异常,先立即中止当前请求
将 readystate 置为4,并触发 xhr.onreadystatechange事件
如果上传阶段还没有结束,则依次触发以下事件:
xhr.upload.onprogress
xhr.upload.[onabort或ontimeout或onerror]
xhr.upload.onloadend
触发 xhr.onprogress事件
触发 xhr.[onabort或ontimeout或onerror]事件
触发xhr.onloadend 事件
在哪个xhr事件中注册成功回调?

从上面介绍的事件中,可以知道若xhr请求成功,就会触发xhr.onreadystatechange和xhr.onload两个事件。 那么我们到底要将成功回调注册在哪个事件中呢?我倾向于 xhr.onload事件,因为xhr.onreadystatechange是每次xhr.readyState变化时都会触发,而不是xhr.readyState=4时才触发。

xhr.onload = function () {
    //如果请求成功
    if(xhr.status == 200){
      //do successCallback
    }
  }

上面的示例代码是很常见的写法:先判断http状态码是否是200,如果是,则认为请求是成功的,接着执行成功回调。这样的判断是有坑儿的,比如当返回的http状态码不是200,而是201时,请求虽然也是成功的,但并没有执行成功回调逻辑。所以更靠谱的判断方法应该是:当http状态码为2xx或304时才认为成功。

  xhr.onload = function () {
    //如果请求成功
    if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
      //do successCallback
    }
  }

参考文章

你真的会使用XMLHttpRequest吗?
文件上传的渐进式增强

@cy0707 cy0707 added the Ajax label Jan 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant