前端通訊:ajax設計方案(八)--- 設計請求池,複用請求,讓前端通訊快、更快、再快一點

直接進入主題,本篇文章有點長,包括從設計階段,到摸索階段,再到實現階段,最後全面覆蓋測試階段(包括數據蒐集清洗),還有與主流前端通訊框架進行對比PK階段。html

首先介紹一下一些概念:前端

  1. 瀏覽器的併發能力:瀏覽器設計當初就定義了瀏覽器打開頁面,同時發送http請求的瞬時數量。這樣設計有不少緣由,同時保護瀏覽器和服務器。具體能夠谷歌或者百度關鍵字:瀏覽器併發。node

  2. 瀏覽器針對服務器域名請求的併發限制數量:jquery

    

  3. 請求池:相似於數據庫鏈接池同樣,對數據庫請求鏈接進行分配管理等等ios

  4. 複用請求:對於生命週期已經結束的請求不進行銷燬,重複利用,減小從新向瀏覽器申請資源,減小通用庫設計中的通用檢查。git

 

引入新功能:鏈接池github

 

當初爲啥要作這個功能:ajax

  上次迭代最後留了一個彩蛋,我在一次測試中,發送完成一個請求以後,將這個請求存到一個變量中,而後又嘗試從新打開,發現居然能夠發送出去。好奇心害死貓,我在瀏覽器中用console.time去計算執行這段js代碼的時間,發現重複使用的請求使用時間大概是前者的一半,甚至更少。既然,他能夠這麼優秀,那我爲啥不能欲罷不能呢。因此,這一步下去就是一波三折...chrome

 

摸索思路:數據庫

 

 

按着當時的設計思路一步一步的來確認

   1. 首先確認了一個請求發送完了以後,能夠再次打開而後發送。這樣就保證了方向是對的,能夠繼續優化下去

   2. 其次,對於一個域名,瀏覽器的同時發送請求的數目就如上面圖片上描述的,主流基本都是6個,因此請求池將基於瀏覽器最大併發作鏈接的配置,不過由於瀏覽器差別,會將鏈接池的鏈接數量作配置進行配置

   3. 將這個請求池塞滿配置的請求將是第一任務,對於XMLHttpRequest這個對象,主流的想法都是作一個拷貝,當前淺拷貝是不行的,那就作了一次深度拷貝測試,塞滿請求池。不過,該方案夭折了。

   4. 由於請求對象的原型是XMLHttpRequest,這個對象實現了瀏覽器的接口才能經過瀏覽器API發送http請求,可是深度拷貝的對象是Object,object不實現瀏覽器的接口,因此夭折了,沒法直接拷貝,難道我要相似於發送6次空請求以後去搜集請求池鏈接嗎?這個是絕對不可能饒恕的行爲,因此作了下一步的摸索

   5. 若是XMLHttpRequest對象實現了瀏覽器的接口,那麼咱們將深度拷貝的對象的原型的指向從新指向到XMLHttpRequest。這樣是否是能夠發送請求了。而後測試了,阿西吧,非法請求,並且對於XMLHttpRequest對象中的response、responseText等熟悉仍是隻讀了,從新拷貝賦值瀏覽器報錯,非法,全都是非法。

   6. 氣餒了好久,把大腦放空換個思路去解決這個問題。參考了其餘相似請求池的設計思路,要麼一開始就作初始化,將池子中的鏈接一次性生產出來,或者每次創建的鏈接消費完畢以後再納入池子中,等待鏈接滿了以後才啓用鏈接池。

   7. 考慮到前端每一個頁面,或者對於如今的每一個組件中能發送請求的數量並非很大,可能一個路由下就發送了4-5個請求就解決一個小業務需求,根本達不到啓動請求池的基本配置。其次,經過瀏覽器的performance監控頁面性能,發現初始化的時間就在幾毫秒之間,相對於後期使用加速,這個時間的付出,對於後期的使用是很值得的預算。因此,選擇了第一種方式,一次性初始化請求池,等待調用。下面縷清思惟,作了更完整和全面的設計。

 

請求池設計方案:

左邊 -- 生成請求池部分 

  1. 加載ajax-js庫的時候,經過判斷請求池開關是否打開,打開的話就走初始化這一套流程。

  2. 建立基於全局配置的空請求-->copy所需參數-->建立空XMLHttpRequest對象-->合併copy參數到空對象上-->讀取配置數量生成請求池

  3. 由於請求池是基於全局配置生成的請求,因此若是全局配置變更,將觸發請求池的reload,從新生成。

 

中間 -- 經過請求池發送請求的生命週期

  1. 請求進來,首先判斷開關是否打開,打開的話就先判斷請求池中請求的數量是否還有。

  2. 對比參數,是否有額外預設參數的變動(除url、data、successEvent、eroorEvent外),若是有將在請求發送前進行設置。(例如:request的Header將在每次開啓的時候所有從新置空)

  3. 在請求的生命週期結束以後,而後歸還請求池。注意:請求的生命週期結束有不少情況,發送錯誤url、timeout超時等在瀏覽器層面就失敗的,以及請求發送成功時候,非200、304等錯誤,好比4XX或者5XX系列錯誤的生命週期結束

  4. 最後判斷是否有排隊的請求,有的話取出再來,沒有直接返回池子

 

右邊 -- 請求排隊系統

  1. 對於請求若是超出請求池數量,請求池確定沒法承載,就算能承載,其實瀏覽器也是將這些超出併發的請求進行排隊,等待一波結束以後再發送。因此有個排隊的系統很重要的啦,來管理請求。

  2. 對於超出容量請求,首先排隊。而後,在請求生命結束以後,將會去檢查排隊系統是否還有排隊請求。其次,若是有就進行取出,再走請求的生命週期,若是每頁,那就沒請求啦

 

代碼片斷展現:

1. 加載類庫時判斷開關,初始化代碼

 1   var outputObj = function () {
 2     //雖然在IE六、7上能夠支持,可是最好升級你的瀏覽器,畢竟xp已經淘汰,面向將來吧,騷年,和我一塊兒努力吧!!
 3     if (tool.getIEVersion() < 7) {
 4       //實在不想說:升級你的瀏覽器吧
 5       throw new Error("Sorry,please update your browser.(IE8+)");
 6     }
 7 
 8     // 是否開啓鏈接池
 9     if (initParam.pool.isOpen) {
10       tool.createPool()
11     }
12 
13     return tempObj;
14   };
View Code

 2. 建立方法

1     // 建立請求池中連接
2     createPool: function () {
3       // IE 系列不支持發送請求傳'',因此默認/
4       tempObj.common({url: '/'}, true)
5       tool.deepCloneXhr(selfData.xhr, initParam.pool.requestNumber)
6     },
View Code

3. 拷貝通用參數存入請求池

 1     // 拷貝xhr參數
 2     deepCloneXhr: function (data, requestNum) {
 3       var mapping = {
 4         currentUrl: true,
 5         onerror: true,
 6         onload: true,
 7         onreadystatechange: true,
 8         ontimeout: true,
 9         timeout: true,               // IE系列只有open鏈接以後才支持覆蓋
10         withCredentials: true,
11         xhr_ie8: true
12       }
13       var temp = {}
14 
15       for (var key in data) {
16         if (mapping[key]) {
17           if (!isNaN(tool.getIEVersion()) && key !== 'timeout') {
18             temp[key] = data[key]
19           } else {
20             var newKey = '_' + key
21             temp[newKey] = data[key]
22           }
23         }
24       }
25 
26       for (var i = 0; i < requestNum; i++) {
27         var nullRequest = tool.createXhrObject()
28         tool.MergeObject(nullRequest, temp)
29         selfData.requestPool.push(nullRequest)
30       }
31     },
View Code

4. 使用加速,判斷是否開啓(只舉例post)

 1     //異步post請求
 2     post: function (url, data, successEvent, errorEvent, timeoutEvent) {
 3       var ajaxParam = {
 4         type: "post",
 5         url: url,
 6         data: data,
 7         contentType: '',
 8         successEvent: successEvent,
 9         errorEvent: errorEvent,
10         timeoutEvent: timeoutEvent
11       };
12 
13       if (initParam.pool.isOpen) {
14         tool.useRequestPool(ajaxParam)
15       } else {
16         tempObj.common(ajaxParam);
17       }
18     },
View Code

5. 使用請求池連接,設置基本配置(url,datda,successEvent)

 1     // 請求池申請請求使用
 2     useRequestPool: function (param) {
 3       // 判斷請求池中是否有可用請求
 4       if (selfData.requestPool.length !== 0) {
 5         var temp = selfData.requestPool.shift(), sendData = '', tempHeader = {}
 6         // 賦值操做,將數據捆綁到原型上
 7         temp.callback_success = param.successEvent
 8         temp.callback_error = param.errorEvent
 9         temp.callback_timeout = param.timeoutEvent
10         temp.data = param.data
11 
12         // 處理參數
13         switch (param.contentType) {
14           case '':
15             tool.each(tool.MergeObject(param.data, initParam.publicData), function (item, index) {
16               sendData += (index + "=" + item + "&")
17             });
18             sendData = sendData.slice(0, -1);
19             break
20           case 'json':
21             sendData = JSON.stringify(tool.MergeObject(param.data, initParam.publicData))
22             break
23           case 'form':
24             if (!tool.isEmptyObject(initParam.publicData)) {
25               tool.each(initParam.publicData, function (item, index) {
26                 param.data.append(index, item)
27               })
28             }
29             sendData = param.data
30             break
31         }
32 
33         //判斷請求類型
34         if (param.type === 'get') {
35           temp.open(param.type, tool.checkRealUrl(param.url, temp) + (sendData === '' ? '' : ('?' + sendData)))
36         } else {
37           temp.open(param.type, tool.checkRealUrl(param.url, temp))
38         }
39 
40         param.responseType ? (temp.responseType = param.responseType) : null
41 
42         if (!isNaN(tool.getIEVersion())) {
43           temp.timeout = temp._timeout
44         }
45 
46         switch (param.contentType) {
47           case '':
48             tempHeader['Content-Type'] = 'application/x-www-form-urlencoded'
49             break
50           case 'json':
51             tempHeader['Content-Type'] = 'application/json'
52             break
53         }
54 
55         //設置http協議的頭部
56         tool.each(tool.MergeObject(tempHeader, initParam.requestHeader), function (item, index) {
57           temp.setRequestHeader(index, item)
58         });
59 
60         //發送請求
61         temp.send(param.type === 'get' ? '' : sendData);
62       } else {
63         // 沒有請求,加載到待發送隊列中
64         selfData.queuePool.push(param)
65       }
66     },
View Code

6. 在默認週期中回收連接(onreadystatechange和onload)

 1       //xmlhttprequest每次變化一個狀態所監控的事件(可拓展)
 2       xhr.onreadystatechange = function () {
 3         switch (this.readyState) {
 4           case 1://打開
 5             //do something
 6             break;
 7           case 2://獲取header
 8             //do something
 9             break;
10           case 3://請求
11             //do something
12             break;
13           case 4://完成
14             //在ie8下面,無xhr的onload事件,只能放在此到處理回調結果
15             if (this.xhr_ie8) {
16               if (this.status === 200 || this.status === 304) {
17                 if (this.responseType == "json") {
18                   this.callback_success ?
19                     this.callback_success(ajaxSetting.transformResponse(JSON.parse(this.responseText))) :
20                     ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(this.responseText)))
21                 } else {
22                   this.callback_success ?
23                     this.callback_success(ajaxSetting.transformResponse(this.responseText)) :
24                     ajaxSetting.successEvent(ajaxSetting.transformResponse(this.responseText))
25                 }
26               } else {
27                 // 請求錯誤蒐集
28                 tool.uploadAjaxError({
29                   type: 'request',
30                   errInfo: JSON.stringify(this.data ? this.data : ajaxSetting.data),
31                   errUrl: this.currentUrl,
32                   errLine: this.status,
33                   Browser: navigator.userAgent
34                 })
35               }
36               // 針對IE8 請求池處理
37               if (ajaxSetting.pool.isOpen) {
38                 tool.responseOver(this)
39               }
40             } else {
41               if (this.status === 0) {
42                 // 發送不存在請求,將不會走onload,直接這裏就掛了,請求歸還請求池
43                 if (ajaxSetting.pool.isOpen) {
44                   tool.responseOver(this)
45                 }
46               }
47             }
48             break;
49         }
50         ;
51       };
View Code
 1       //onload事件(IE8下沒有該事件)
 2       xhr.onload = function (e) {
 3         if (this.readyState === 4 && (this.status == 200 || this.status == 304)) {
 4           /*
 5           *  ie瀏覽器全系列不支持responseType='json'和response取值,因此在ie下使用JSON.parse進行轉換
 6           * */
 7           if (!isNaN(tool.getIEVersion())) {
 8             if (this.responseType === 'json') {
 9               this.callback_success ?
10                 this.callback_success(ajaxSetting.transformResponse(JSON.parse(this.responseText))) :
11                 ajaxSetting.successEvent(ajaxSetting.transformResponse(JSON.parse(this.responseText)));
12             } else {
13               this.callback_success ?
14                 this.callback_success(ajaxSetting.transformResponse(this.responseText)) :
15                 ajaxSetting.successEvent(ajaxSetting.transformResponse(this.responseText));
16             }
17           } else {
18             this.callback_success ?
19               this.callback_success(ajaxSetting.transformResponse(this.response)) :
20               ajaxSetting.successEvent(ajaxSetting.transformResponse(this.response));
21           }
22         } else {
23           /*
24            *  這邊爲了兼容IE八、9的問題,以及請求完成而形成的其餘錯誤,好比404等
25            *   若是跨域請求在IE八、9下跨域失敗不走onerror方法
26            *       其餘支持了Level 2 的版本 直接走onerror
27            * */
28           this.callback_error ?
29             this.callback_error(e.currentTarget.status, e.currentTarget.statusText) :
30             ajaxSetting.errorEvent(e.currentTarget.status, e.currentTarget.statusText);
31 
32           // 請求錯誤蒐集
33           tool.uploadAjaxError({
34             type: 'request',
35             errInfo: JSON.stringify(this.data ? this.data : ajaxSetting.data),
36             errUrl: this.currentUrl,
37             errLine: this.status,
38             Browser: navigator.userAgent
39           })
40         }
41 
42         // 生命週期結束以後返回數據池,不綁定狀態(是否爲成功或失敗狀態)
43         if (ajaxSetting.pool.isOpen) {
44           tool.responseOver(this)
45         }
46       };
View Code

7. 回收方法

1     // 請求週期結束操做
2     responseOver: function (xhr) {
3       selfData.requestPool.push(xhr)
4       if (selfData.queuePool.length > 0) {
5         var tempData = selfData.queuePool.shift()
6         tool.useRequestPool(tempData)
7       }
8     }
View Code

 

如下爲測試結果展現(測試覆蓋面:針對主流瀏覽器測試自身不開請求池、開啓請求池、與主流框架axios、jquery的ajax對比,對比精度連續發送十、100、1000、5000次請求):

 

chrome(測試單位:微秒):

 

 

 

 

 

 

firefox(測試單位:毫秒)  PS:在5000的請求測試下,除了開啓請求池能夠正常進行,其餘方式全都瀏覽器崩潰

 

 

 

 

 

safari(測試單位:微秒):

 

 

 

 

 

opera(測試單位:微秒)

 

 

 

 

 

edge(測試單位:毫秒)

 

 

 

 

 

IE11(測試單位:毫秒)

 

 

 

 

IE9(測試單位:毫秒)

 

 

 

 

 

IE8(測試單位:毫秒)   PS:在IE8下面沒有找到axios ie8下運行方案,jquery就不作測試,就作自身對比測試

 

 

 

 

 

 

 首先陳述幾個問題:

  1. 測試單位取值

    使用瀏覽器的console.time方法去取值,在不一樣瀏覽器得到的參數不同

  2. 爲何不一樣意取值

    由於ajax-js在開啓請求池的用時中達到了0.00X等級,也就是微秒的級別,在折線圖中沒法展現,因此增大單位,好看出趨勢

 

 

從全部清洗數據得到的統計圖中總結如下:

  1. jquery.ajax速度和性能最慢

  2. ajax-js 開啓請求池情況下,性能最好,最快

  3. ajax-js類庫不開啓請求池,相對來講,比jquery中的ajax性能高,可是,不得不認可,axios比ajax-js優秀。

  4. ajax-js類庫開啓請求池的情況下,針對於自身,性能提升至少一倍以上,比axios還快,快不少

 

 

數據蒐集在:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/collect

清洗完數據造成的報表:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/collect

數據清洗的工具:https://github.com/GerryIsWarrior/ajax/tree/master/ajax-dataCenter/tool

清洗完成造成的數據報表能夠直接打開,能夠更清晰的看數據

如需測試,能夠去github下載該項目,在ajax-interface文件下,寫了一個express的node服務器,能夠 npm i 初始化以後,而後 npm run start 啓動。而後在ajax-testing目錄下有個html文件,提供了測試案例和一些簡單demo。

這次對外暴露的方法都開啓了請求池加速(開啓全局加速配置),除了common方法,預留一個未加速通用方法,防止有特殊需求。並且,本次請求池只是針對一個域名的加速,二期,將會增長針對不一樣域名加速的請求池。

 

github地址:https://github.com/GerryIsWarrior/ajax   對你有幫助或啓發,點個小星星,支持繼續研究下去

 

此次的迭代一波三折,中間有不少次走到一半就持續不下去了,可是由於剛開始測試的基礎方向是對的,因此不想放棄,而後都是理順思路一步一步的去走,看本身到底錯在什麼地方,這個方向爲何不能走。還有爲了此次測試,作了大量的數據蒐集,從mac系統和window不停切換,爲主流瀏覽器作兼容測試和主流框架對比測試數據蒐集。蒐集完成以後,耐心的作數據清洗,將混雜的數據,進行分類概括,而後找對應報表,最後將數據加載到報表上,作更直觀的展現。

 

過程是艱難的,結果是值得興奮的。正視本身的不足,axios是一個優秀的框架,速度和性能絕對是很好的。在沒有作請求池優化以前,本身寫的庫達不到那個程度,不過在開啓了請求池以後,在性能和速度方面已經超過了axios,jquery就不談了。在研究前端通訊的過程當中收穫了不少不少,從基礎到設計,從底層到優化,沒有什麼是解決不了的問題。認清本身,作更好的本身,沒有什麼是不可能的。

從0-1是艱難的,從99-100更是難上加難。可是,只要去作,終是離那個方向更近一點,前端共勉!!

 

下一步迭代:更完備的文檔和更完備的測試

相關文章
相關標籤/搜索