首先要明確的是對事件的監聽方法是在 on + 事件名,比如load事件,load事件的監聽方法就是onload,也可以使用addEventListener方法,這個方法的參數就是事件名稱('load')
言歸正傳,Progress Events規范是W3C的一個草案,定義了與客戶端服務器通信有關的事件。有如下7個進度事件:
- abort:在因為調用abort()方法而終止連接時觸發。
- error:在請求發生錯誤時觸發。
- load: 在接收到完整的響應數據時觸發。
- loadend: 在通信完成或者觸發error、abort、或load事件后觸發。
- loadstart:在接收到響應數據的第一個字節時觸發。
- progress:在接收響應期間持續不斷地觸發。
- 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種事件。
- 如果請求超時,會觸發timeout事件。
- 如果請求中止,會觸發abort事件。
- 最后,像太多重定向這樣的網絡錯誤會阻止請求完成,但這些情況發生時會觸發error事件
可以通過調用XMLHttpRequest對象的abort()方法來取消正在進行的HTTP請求。調用abort()方法在這個對象上觸發abort事件
調用abort()的主要原因是完成取消或超時請求消耗的時間太長或當響應變得無關時。假如使用XMLHttpRequest為文本輸入域請求自動完成推薦。如果用戶在服務器的建議達到之前輸入了新字符,這時等待請求不再有用,應該中止。
XHR對象的timeout屬性等於一個整數,表示多少毫秒后,如果請求仍然沒有得到結果,就會自動終止。該屬性默認等於0,表示沒有時間限制
四:總結
Angular的http模塊對XHR對象進行了封裝,使得XHR對象的原生進度事件沒有暴露給開發人員,目前的開發中還沒有遇到需要監聽的情況,不知道以后會怎么樣。
作成: 2019-02-09
修改:
- 2019-02-10 23:25:02 添加分類
- 2019-02-16 18:22:15 添加說明
參考:https://www.cnblogs.com/xiaohuochai/p/6552674.html
