XHR对象的进度事件

首先要明确的是对事件的监听方法是在 on + 事件名,比如load事件,load事件的监听方法就是onload,也可以使用addEventListener方法,这个方法的参数就是事件名称('load')

言归正传,Progress Events规范是W3C的一个草案,定义了与客户端服务器通信有关的事件。有如下7个进度事件:

  1. abort:在因为调用abort()方法而终止连接时触发。
  2. error:在请求发生错误时触发。
  3. load: 在接收到完整的响应数据时触发。
  4. loadend: 在通信完成或者触发error、abort、或load事件后触发。
  5. loadstart:在接收到响应数据的第一个字节时触发。
  6. progress:在接收响应期间持续不断地触发。
  7. timeout:在规定的时间内浏览器还没有接收到响应时触发。

  每个请求都从触发loadstart事件开始,接下来,通常每隔50毫秒左右触发一次progress事件,然后触发load、error、abort或timeout事件中的一个,最后以触发loadend事件结束。

  对于任何具体请求,浏览器将只会触发load、abort、timeout和error事件中的一个。XHR2规范草案指出一旦这些事件中的一个发生后,浏览器应该触发loadend事件

对应的监听方法如下:

1     onabort: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
2     onerror: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
3     onload: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
4     onloadend: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
5     onloadstart: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
6     onprogress: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
7     ontimeout: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;

  注意到监听方法都支持传递2个参数,this对象指向当前的XHR实例(当然,这里不能使用箭头函数),ev是进度事件的实例。ev.target也指向当前XHR实例。

一、load事件

  load事件在接收到完整的响应数据时触发。那么就没有必要监听readystatechange事件了。并且只有完整的接收到才会触发,所以也没有必要检查readyState属性了。

  Angular的http模块是都XHR对象的再封装,其中主要的处理响应的逻辑就是放在load事件中。下面是简化的逻辑:

  1     this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
  2       // 创建XHR对象
  3       const _xhr: XMLHttpRequest = browserXHR.build();
  4       // 调用open方法准备请求
  5       _xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
  6 
  7       // 定义load事件
  8       const onLoad = () => {
  9 
 10         // 下面的一大段代码是为了提供友好的Response,
 11         // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
 12         let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
 13 
 14         let body: any = null;
 15 
 16         // HTTP 204 means no content
 17         if (status !== 204) {
 18           // responseText is the old-school way of retrieving response (supported by IE8 & 9)
 19           // response/responseType properties were introduced in ResourceLoader Level2 spec
 20           // (supported by IE10)
 21           body = (typeof _xhr.response === 'undefined') ? _xhr.responseText : _xhr.response;
 22 
 23           // Implicitly strip a potential XSSI prefix.
 24           if (typeof body === 'string') {
 25             body = body.replace(XSSI_PREFIX, '');
 26           }
 27         }
 28 
 29         // fix status code when it is 0 (0 status is undocumented).
 30         // Occurs when accessing file resources or on Android 4.1 stock browser
 31         // while retrieving files from application cache.
 32         if (status === 0) {
 33           status = body ? 200 : 0;
 34         }
 35 
 36         const headers: Headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
 37         // IE 9 does not provide the way to get URL of response
 38         const url = getResponseURL(_xhr) || req.url;
 39         const statusText: string = _xhr.statusText || 'OK';
 40 
 41         let responseOptions = new ResponseOptions({body, status, headers, statusText, url});
 42         if (baseResponseOptions != null) {
 43           responseOptions = baseResponseOptions.merge(responseOptions);
 44         }
 45         const response = new Response(responseOptions);
 46         response.ok = isSuccess(status);
 47         if (response.ok) {
 48           // 这里把response发送出去
 49           responseObserver.next(response);
 50           // TODO(gdi2290): defer complete if array buffer until done
 51           responseObserver.complete();
 52           return;
 53         }
 54         // 如果发生错误了,把错误发送出去
 55         responseObserver.error(response);
 56       };
 57       // 定义error事件
 58       const onError = (err: ErrorEvent) => {
 59         // 对Error再封装
 60         let responseOptions = new ResponseOptions({
 61           body: err,
 62           type: ResponseType.Error,
 63           status: _xhr.status,
 64           statusText: _xhr.statusText,
 65         });
 66         if (baseResponseOptions != null) {
 67           responseOptions = baseResponseOptions.merge(responseOptions);
 68         }
 69         // 把通信的错误发送出去
 70         responseObserver.error(new Response(responseOptions));
 71       };
 72       // 设置content-type
 73       this.setDetectedContentType(req, _xhr);
 74 
 75       // 设置request的Header
 76       if (req.headers == null) {
 77         req.headers = new Headers();
 78       }
 79       if (!req.headers.has('Accept')) {
 80         req.headers.append('Accept', 'application/json, text/plain, */*');
 81       }
 82       req.headers.forEach((values, name) => _xhr.setRequestHeader(name !, values.join(',')));
 83 
 84       // 设置responseType
 85       if (req.responseType != null && _xhr.responseType != null) {
 86         switch (req.responseType) {
 87           case ResponseContentType.ArrayBuffer:
 88             _xhr.responseType = 'arraybuffer';
 89             break;
 90           case ResponseContentType.Json:
 91             _xhr.responseType = 'json';
 92             break;
 93           case ResponseContentType.Text:
 94             _xhr.responseType = 'text';
 95             break;
 96           case ResponseContentType.Blob:
 97             _xhr.responseType = 'blob';
 98             break;
 99           default:
100             throw new Error('The selected responseType is not supported');
101         }
102       }
103       // 添加监听事件
104       _xhr.addEventListener('load', onLoad); // 没有直接使用xhr.onload
105       _xhr.addEventListener('error', onError);
106 
107       // 发起请求
108       _xhr.send(this.request.getBody());
109 
110       // 这是一个Observable,在退订Observable要对XHR对象进行解引用操作
111       return () => {
112         _xhr.removeEventListener('load', onLoad);
113         _xhr.removeEventListener('error', onError);
114         _xhr.abort();
115       };
116     });

 

可以看到,Angular把XHR对象进行了再封装,返回的是一个Observable,使用Angular通过的http模块是不能直接监听load事件的,可以认为只要Response Observable发送出数据就已经触发了load事件

二、progress事件

  progress事件在接收响应期间持续不断地触发。这里把progress事件说明一下是因为Angular中通过了一个监听进度事件的接口,下面我们来看一下实现原理:

 

 1       // 通过设置参数来开启进度监听
 2       if (req.reportProgress) {
 3         // 取得数据时一直监听
 4         xhr.addEventListener('progress', onDownProgress);
 5 
 6         // 上传数据时要判断是否有upload
 7         if (reqBody !== null && xhr.upload) {
 8           xhr.upload.addEventListener('progress', onUpProgress);
 9         }
10       }

其中onDownProgress和onUpProgress方法中提供了友好的事件类型,实际上不考虑友好,单纯的使用上可以直接发送event出去。

三、其他事件

  HTTP请求无法完成有3种情况,对应3种事件。

  1. 如果请求超时,会触发timeout事件。
  2. 如果请求中止,会触发abort事件。
  3. 最后,像太多重定向这样的网络错误会阻止请求完成,但这些情况发生时会触发error事件

  可以通过调用XMLHttpRequest对象的abort()方法来取消正在进行的HTTP请求。调用abort()方法在这个对象上触发abort事件

  调用abort()的主要原因是完成取消或超时请求消耗的时间太长或当响应变得无关时。假如使用XMLHttpRequest为文本输入域请求自动完成推荐。如果用户在服务器的建议达到之前输入了新字符,这时等待请求不再有用,应该中止。

  XHR对象的timeout属性等于一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。该属性默认等于0,表示没有时间限制

四:总结

  Angular的http模块对XHR对象进行了封装,使得XHR对象的原生进度事件没有暴露给开发人员,目前的开发中还没有遇到需要监听的情况,不知道以后会怎么样。

作成: 2019-02-09

修改:

  1. 2019-02-10 23:25:02 添加分类
  2. 2019-02-16 18:22:15 添加说明

参考:https://www.cnblogs.com/xiaohuochai/p/6552674.html

posted on 2019-02-09 01:39  西门本不吹雪  阅读(3866)  评论(0编辑  收藏  举报