改善程序性能和代碼質量:通過代理模式組合HTTP請求
原文:https://levelup.gitconnected.com/improve-both-app-performance-and-code-quality-combining-http-requests-by-proxy-pattern-2cce132d60
作者:bitfish
在前端項目中,我們的網頁通常需要向服務器發送多個HTTP請求。
假設我們的產品具有一項功能,即每當用戶單擊 li 標記時,客戶端都會向服務器發送一個HTTP請求。
這是一個簡單的Demo:
- <html>
- <body>
- <ul>
- <li>1</li>
- <li>2</li>
- <li>3</li>
- <li>4</li>
- <li>5</li>
- <li>6</li>
- <li>7</li>
- <li>8</li>
- <li>9</li>
- </ul>
- <script>
- // Suppose this function is used to make HTTP requests to the server
- var sendHTTPRequest = function(message) {
- console.log('Start sending HTTP message to the server: ', message)
- console.log('1000ms passed')
- console.log('HTTP Request is completed')
- }
- var ul = document.getElementsByTagName('ul')[0];
- ul.onclick = function(event) {
- if (event.target.nodeName === "LI") {
- // Executes this function every time the <li> tag is clicked.
- sendHTTPRequest(event.target.innerText)
- }
- }
- </script>
- </body>
- </html>
在上面的代碼中,我們直接使用簡單的 sendHTTPRequest 函數來模擬發送HTTP請求。這樣做是為了更好地專注于核心目標,因此我簡化了一些代碼。
在上面的代碼中,我們直接使用簡單的 sendHTTPRequest 函數來模擬發送HTTP請求。這樣做是為了更好地專注于核心目標,因此我簡化了一些代碼。
上面的程序是這樣的:
為了使你們更容易嘗試,我制作了一個Codepen演示:https://codepen.io/bitfishxyz/pen/PobOZMm
當然,在真實的項目中,我們可能會向服務器發送一個文件,推送通知,或者發送一些日志。但為了演示的慣例,我們將跳過這些細節。
好了,這是一個很簡單的演示,那么上面的代碼有沒有什么缺點呢?
如果您的項目非常簡單,那么編寫這樣的代碼應該沒有問題。但是,如果您的項目很復雜,并且客戶端需要頻繁向服務器發送HTTP請求,則此代碼效率很低。
在上面的示例中,如果任何用戶反復快速單擊 li 元素會發生什么?這時,我們的客戶端需要向服務器發出頻繁的HTTP請求,并且每個請求都會消耗大量時間和服務器資源。
客戶端每次與服務器建立新的HTTP連接時,都會消耗一些時間和服務器資源。因此,在HTTP傳輸機制中,一次傳輸所有文件比多次傳輸少量文件更為有效。
例如,您可能需要發送五個HTTP請求,每個HTTP請求的HTTP數據包大小為1MB。現在,您一次發送一個HTTP請求,數據包大小為5MB。通常預期后者的性能要比前一個更好。
網頁上的大量HTTP請求可能會減慢網頁的加載時間,最終損害用戶體驗。如果加載速度不夠快,這可能會導致訪問者更快地離開該頁面。
因此,在這種情況下,我們可以考慮合并HTTP請求。
在我們目前的項目中,我的思路是這樣的:我們可以在本地設置一個緩存,然后在一定范圍內收集所有需要發送給服務器的消息,然后一起發送。
你可以暫停一下,自己試著想辦法。
提示:您需要創建一個本地緩存對象來收集需要發送的消息。然后,您需要使用定時器定時發送收集到的消息。
這是一個實現。
- var messages = [];
- var timer;
- var sendHTTPRequest = function (message) {
- messages.push(message);
- if (timer) {
- return;
- }
- timer = setTimeout(function () {
- console.log("Start sending messages: ", messages.join(","));
- console.log("1000ms passed");
- console.log("HTTP Request is completed.");
- clearTimeout(timer);
- timer = null;
- messages = [];
- }, 2000);
- };
每當客戶端需要發送消息,也就是觸發一個 onclick 事件的時候,sendHTTPRequest 并不會立即向服務器發送消息,而是先將消息緩存在消息中。然后,我們有一個計時器,該計時器在2秒鐘后執行,并且在2秒鐘后,該計時器會將所有先前緩存的消息發送到服務器。此更改達到了組合HTTP請求的目的。
測試結果如下:
如你所見,盡管我們多次觸發點擊事件,但在兩秒鐘內,我們只發送了一個HTTP請求。
當然,為了方便演示,我將等待時間設置為2秒。如果你覺得這個等待時間太長,你可以縮短這個等待時間。
對于不需要太多實時交互的項目,2秒的延遲并不是一個巨大的副作用,但它可以減輕服務器的很多壓力。在適當的情況下,這是非常值得的。
上面的代碼確實為項目提供了一些性能改進。但是就代碼設計而言,上面的代碼并不好。
第一,違反了單一責任原則。sendHTTPRequest 函數不僅向服務器發送HTTP請求,而且還組合HTTP請求。該函數執行過多操作,使代碼看起來非常復雜。
如果某個功能(或對象)承擔了過多的責任,那么當我們的需求發生變化時,該功能通常將不得不發生重大變化。這樣的設計不能有效地應對可能的更改,這是一個糟糕的設計。
我們理想的代碼如下所示:
我們沒有對 sendHTTPRequest 進行任何更改,而是選擇為其提供代理。這個代理函數執行合并HTTP請求的任務,并將合并后的消息傳遞給 sendHTTPRequest 發送。然后我們以后就可以直接使用 proxySendHTTPRequest 方法了。
您可以暫停片刻,然后嘗試自己解決。
這是一個實現:
- var proxySendHTTPRequest = (function() {
- var messages = [],
- timer;
- return function(message) {
- messages.push(message);
- if (timer) {
- return;
- }
- timer = setTimeout(function() {
- sendHTTPRequest(messages.join(","));
- clearTimeout(timer);
- timer = null;
- messages = [];
- }, 2000);
- };
- })();
其基本思想與前面的代碼類似,該代碼使用 messages 變量在一定時間內緩存所有消息,然后通過計時器統一地發送它們。此外,這段代碼使用了閉包技巧,將 messages 和 timer 變量放在局部作用域中,以避免污染全局名稱空間。
這段代碼與前面的代碼最大的區別是它沒有更改 sendHTTPRequest 函數,而是將其隱藏在 proxySendHTTPRequest 后面。我們不再需要直接訪問 sendHTTPRequest,而是使用代理 proxySendHTTPRequest 來訪問它。proxySendHTTPRequest 與sendHTTPRequest 具有相同的參數列表和相同的返回值。
這樣的設計有什么好處?
- 發送HTTP請求和合并HTTP請求的任務交給了兩個不同的函數,每個函數專注于一個職責。它遵從單一責任原則,并使代碼更容易理解。
- 由于兩個函數的參數是相同的,我們可以簡單地用 proxySendHTTPRequest 替換 sendHTTPRequest 的位置,而不需要做任何重大更改。
想象一下,如果將來網絡性能有所提高,或者由于某些其他原因,我們不再需要合并HTTP請求。在這一點上,如果我們使用以前的設計,我們將不得不再次大規模地更改代碼。在當前的代碼設計中,我們可以簡單地替換函數名。
事實上,這個編碼技巧通常被稱為設計模式中的代理模式。
所謂的代理模式,其實在現實生活中很好理解。
- 比方說,你想訪問一個網站,但你不想泄露你的IP地址。那么你可以使用,先訪問你的代理服務器,然后通過代理服務器訪問目標網站。這樣目標網站就無法知道你的IP地址了。
- 有時候,你會把你的真實服務器隱藏在Nginx服務器后面,讓Nginx服務器為你的真實服務器處理一些瑣碎的操作。
這些都是現實生活中代理模式的例子。
我們不需要為代理模式(或任何其他設計模式)的正式定義而煩惱,我們只需要知道,當客戶端沒有直接訪問它的便利(或能力)時,我們可以提供代理功能(或對象)來控制對目標功能(或對象)的訪問即可。客戶機實際上訪問代理函數(或對象),代理函數對請求進行一些處理,然后將請求傳遞給目標。
本文轉載自微信公眾號「前端全棧開發者」,可以通過以下二維碼關注。轉載本文請聯系前端全棧開發者公眾號。