Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

onreadystatechange触发时机的问题

  XHR对象的onreadystatechange事件想必大家都很了解,在onload事件普及前一直都靠它来判断加载状态的。这个事件如果只是作为onload的兼容用法就没有触发时机的问题。如果需求确实需要明确地监听请求的每个状态变化状况,那就会遇到很大问题。

声明

  本文使用的测试浏览器为:Chrome39、Firefox33、IE11,测试环境为:Windows7。

UNSENT(0) -> OPENED(1)

  XHR对象的readyState属性初始为0,当调用open方法后变为1。这是在所有浏览器上都一致的行为。

OPENED(1) -> HEADERS_RECEIVED(2)

  在规范中,readyState等于2时表示服务器已经响应,客户端已经接收到了服务器的所有响应头。言下之意就是在这之后服务器不可能还会发送其它头,只剩数据部分了。所以服务器响应100状态码的话XHR对象是不会进入这个状态的,因为100状态码之后还可能有别的头。到这里的行为依然是所有浏览器都一致。
  接下来就要开始蛋疼了,也可以说是一些浏览器BUG了。根据HTTP的定义,每个响应头都占一行,遇到空行表示头部分结束。但在遇到空行时,Chrome和Firefox都没有认为头部分结束,所以不进入这个状态。只有IE的行为是正确的。
  • Firefox:接收到数据实体的第一个包时进入这个状态
  • Chrome:整个数据实体接收完成后进入这个状态

HEADERS_RECEIVED(2) -> LOADING(3)

  在规范中,readyState等于3时表示数据开始接收。对于这部分,IE和Firefox的行为都是正确的,他们都在接收到第一个数据实体的包时进入了这个状态。而Chrome还是同上面一样的问题,要等整个数据实体都接收完成后才会进入这个状态。
  至于这个状态上的onreadystatechange事件是否对每个数据包都触发一次我没找到相关的定义。

LOADING(3) -> DONE(4)

  这是最后一步,输出传输完成或错误时进入的步骤。这个步骤在所有浏览器上的触发时机都是正确的,所以我前面才说如果把onreadystatechange作为onload的兼容版本来使用的话不会遇到触发时机不对的问题。但神奇的Chrome是到这时候才开始处理前面两个步骤的,所以Chrome上的结果是先进入状态1,然后等数据加载完成后瞬间冒出234

测试程序

下面提供一个NodeJS写的测试程序,需要开启 harmonyuse_strict//nodejs
require('http').createServer(function(request,response){
  if(request.url=='/test'){
    let connection=request.connection;
    let generator=new function*(){
      //输出100状态码头(延迟2秒完成)
      yield new Promise(function(resolve){
        connection.write([
          'HTTP/1.1 100 Continue',
          '',
        ].join('\r\n')+'\r\n');
        console.log('100');
        setTimeout(resolve,2000);
      });
      //输出200状态码及其它头(延迟2秒完成)
      yield new Promise(function(resolve){
        connection.write([
          'HTTP/1.1 200 OK',
          'Content-Type: text/plain',
          'Content-Length: 11',
          '',
        ].join('\r\n')+'\r\n');
        console.log('200');
        setTimeout(resolve,2000);
      });
      //输出一半数据部分(延迟2秒完成)
      yield new Promise(function(resolve){
        connection.write('HELLO ');
        console.log('HELLO');
        setTimeout(resolve,2000);
      });
      //输出令一半数据部分(延迟2秒完成)
      yield new Promise(function(resolve){
        connection.write('WORLD\r\n');
        console.log('WORLD');
        setTimeout(resolve,2000);
      });
      //关闭连接
      connection.end();
    };
    //处理generator执行
    void function callee(){
      var promise=generator.next().value;
      if(promise)promise.then(callee);
    }();
  }else{ //读取页面
    let path=process.cwd()+request.url;
    return require('fs').readFile(path,function(error,data){
      response.setHeader('Content-Type','text/html');
      if(error)return response.end('error');
      response.end(data);
    });
  };
}).listen(1234);
<!--test.html-->
<script>
var xhr=new XMLHttpRequest;
console.log(xhr.readyState);
xhr.onreadystatechange=function(){
  console.log(xhr.readyState);
};
xhr.open("POST","test");
xhr.send();
</script>

网名:
54.144.24.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^