⚠️ 如果您有光敏性,您可能想跳過此操作。
請參閱下面的靜態圖片,這些燈將開始快速閃爍!
記住標題…我們在這裡討論的是流。
我可以談論協定、資料包、排序、acks 和 nacks…但我們在這裡談論流,正如你可能猜對了(我相信你=D)流…它要么是二進制,要么是字串。
是的,字串在發送之前會被壓縮…但是對於我們在前後端開發中通常關心的內容…字串和二進位。
在下面的範例中,我將使用 JS 流。
雖然 Node 有自己的遺留實現,但我們有辦法處理相同程式碼的流,無論是在前面還是後面。
其他語言有自己處理流的方式,但正如你所看到的......處理它的實際程式碼部分並不復雜(並不是說沒有發生複雜的事情)。
您的前端必須使用多個來源的資料。
雖然您可以透過其 IP/連接埠單獨存取每個來源,但您可以將它們放在 API 閘道後面以便於使用和控制。
檢查連結中的儲存庫,了解如何自己運行它,以便您可以使用它。
https://github.com/Noriller/chunk-busters
後續影片版本:
https://youtu.be/QucaOfFI0fM
您擁有來源,您可以取得、等待和渲染。沖洗並重複。
await fetch1(); handleResult(1); await fetch2(); handleResult(2); ... await fetch9(); handleResult(9);
你可能會認為沒有人會真正這麼做......
在這個例子中,很明顯出了問題,但陷入這個問題並不難。
顯而易見:它很慢。你必須觸發並等待每個請求,如果速度很慢…你必須等待。
您知道您不想單獨等待每個請求......因此您觸發所有請求,然後等待它們完成。
await Promise.all([ fetch1(), fetch2(), ... fetch9(), ]); handleAllResults(results);
這就是你可能會做的事情,所以這很好,對吧?
我的意思是,除非您有一個請求速度很慢……這意味著即使所有其他請求都已完成……您仍然必須等待該請求完成。
你知道你可能有一些較慢的請求,所以你仍然觸發所有請求並等待,但當它們到來時,你已經在可能的情況下對結果做了一些事情,所以當最後一個請求到達時,其他請求已經完成。
await fetch1(); handleResult(1); await fetch2(); handleResult(2); ... await fetch9(); handleResult(9);
這一定是最好的解決方案,對吧?
嗯……有什麼奇怪的嗎?
還記得 v1 嗎?是的...它應該是這樣的:
事實證明,在 http/1 中同一個端點可以擁有的連線數量是有限制的,不僅如此…它還依賴瀏覽器,並且每個瀏覽器可能有不同的限制。
您可能會認為只使用 http/2 就到此為止了…但即使這是一個很好的解決方案,您仍然需要在前端處理多個端點。
有沒有好的解決方案?
讓我們回顧一下 v0,但使用流......
你很聰明,所以你可能已經預料到了這一點,因為警告有點破壞了它......但是,是的......你之前看到的並不是後端生成的所有數據。
無論如何…當我們取得時我們就會渲染。
await Promise.all([ fetch1(), fetch2(), ... fetch9(), ]); handleAllResults(results);
如果我們點擊即將到來的串流,我們就可以對它到來的資料塊做一些事情。 (是的!就像 Chat GPT 之類的一樣。)
即使 v0 是處理這個問題最糟糕的方法,但透過使用流可以大大改善它。即使總等待時間相同,您也可以透過顯示某些內容來欺騙使用者。
http/1 問題仍然是一個問題,但同樣,您已經可以看到事情的來龍去脈。
是的…我不能再拖延了…所以…
或…也許我可以?
你看,前端必須管理太多......如果我們可以將其卸載到後端,那麼您就可以擁有一個端點來處理所有來源。
這解決了前端的複雜性和 http/1 問題。
await Promise.all([ fetch1().then(handleResult), fetch2().then(handleResult), ... fetch9().then(handleResult), ]);
// usually we do this: await fetch(...).then((res) => { // this json call accumulate all the response // that later is returned for you to use return res.json() })
我們呼叫一個 API,它將呼叫所有來源、串流資料、處理數據,並將其傳遞到前端,而前端又會在資料到來時呈現資料。
用於此的程式碼正面和背面基本上相同:
await fetchAll(); handleAllResults(results);
是的……就是這樣(最基本、最簡單的例子)。
我們將字串加入到緩衝區中,解析它,檢查是否有可用的區塊,使用它,然後忘記它。這意味著您可以接收/消耗 TB 級的資料…一次一大塊,而 RAM 很少。
我知道你在想什麼...這很愚蠢......這也是瘋狂......
MOOOOOOOOM 我想要 Websocket!
不,親愛的,我們家裡有網路套接字!
家裡的 Websocket:下一個?
你很聰明,你認為如果來源仍在產生資料......那麼也許我們可以更新一些變數......
透過這種方式,您可以保持一個連線用於獲取更多資料或更改其產生的內容。
是的......我想你可以做到這一點......並且在你的堅持下我做了這個例子。 =D
仍然......這是一個愚蠢的想法,我不知道它在哪裡/是否可以在真實的生產環境中使用。也許如果你回到 MPA 和 Ajax 之間那個尷尬的 JS 階段,在這個階段你有足夠的互動性,但沒有足夠的連接到同一伺服器(某些瀏覽器的限制只有 2 個!),那麼也許?
除此之外,不知道。如果您確實有……請告訴我。
在上面的範例中,注意中間的面板,尤其是「進度邊框」:你可以看到它不斷更新。如果您開啟網路選項卡,您會看到 GET 連線在結束之前從未關閉。您還會看到多個其他請求,這些請求改變了那個仍然存在的連接正在執行的操作…所有這些都使用普通的 http/1。
這個例子是我能做的最基本的例子。我什至使用簡單的字串而不是 JSON,因為它更容易解析。
要使用 JSON,您必須累積字串(出於某種原因,我們必須對後端回應進行 JSON.stringify)。
然後檢查在哪裡打破它,然後解析該值或邊解析邊解析。
對於第一個,請考慮 NDJSON:而不是 JSON 數組,您可以用換行符號分隔對象,然後您可以“更輕鬆地”找到中斷的位置,然後 JSON.parse 每個對象並使用該對象。
對於後者,你可以邊解析邊解析:你知道你在一個數組中,現在它是一個對象,好的第一個鍵,現在它是鍵的值,下一個鍵,跳過那個,下一個鍵......等等……手動製作並不是一件簡單的事情,但這就像在等待時從等待然後渲染到渲染的跳轉,這一切都是關於……除了……規模更小。
人們喜歡託管範例,這個您需要自己運行...我希望現在不將範例託管在某個地方的原因已經清楚,但另一個原因是我們不希望這裡出現任何錯誤,如果您要這樣做的話加入網路錯誤高於一切......好吧......
應該要處理錯誤,但它們確實增加了另一層複雜性。
也許...你可以說取決於...
有些地方串流是答案,但在大多數情況下......await json 就足夠了(更不用說更容易了)。
但是學習流程可以解決一些問題,無論是在前端或後端。
在前端,你總是可以用它來「欺騙」使用者。您可以在某些內容出現時顯示它,然後在出現時顯示更多內容,而不是到處顯示旋轉器,即使這需要一段時間。只要您不阻止用戶與其互動...您甚至可以製作比僅顯示旋轉器「更慢」的東西感覺就像它比任何東西都快實際上更快.
在後端,您可以節省 RAM,因為您可以解析每個資料塊,無論是來自前端、資料庫還是中間的任何其他資料。根據需要處理數據並發送數據,而不必等待整個有效負載,否則會引發 OOM(記憶體不足)錯誤。 GB 甚至 TB 的數據…當然,為什麼不呢?
React 慢嗎?整個範例前端是用 React 完成的,除了所有閃爍的「燈」發生的「主要」事情之外,還有很多其他事情發生。
是的......如果你走得夠快,範例就無法跟上並開始凍結。但由於每分鐘很容易進行數千個渲染……我確實認為這對於大多數應用程式來說已經足夠了。
並且,您始終可以提高性能:對於“進度邊框”,如果您需要在渲染中保存一些內容,我使用了延遲值來使其更加平滑......我可以為“燈光”完成此操作以及其他性能增強和標題,但它只會讓“燈”在很多時候停止閃爍(這不會成為一個很好的演示),而且標題中的“電動”下劃線也不會那麼有趣。
在這個例子中,所有這些「改進」都不是理想的,但對於正常的應用程式...你可以讓它處理很多事情。如果您確實需要更多東西,那麼在這種情況下請使用其他解決方案。
將流添加到您的武器庫中......它可能不是萬能的解決方案,但有一天它肯定會派上用場。
如果你想用它做點什麼並且需要幫助,那麼…也許可以打電話給我。 =P
以上是Chunk-Busters:不要跨越溪流!的詳細內容。更多資訊請關注PHP中文網其他相關文章!