背景
大家好,我是江辰,最近小小的實現(xiàn)了下 chatGPT 的問答式回復(fù),調(diào)研了前端如何實現(xiàn)這種問答式請求,有幾種方案,Http、EventSource、WebSocket,三種實現(xiàn)方案各有優(yōu)缺點,Http 和 WebSocket ,想必大家耳聞能詳,這里我講講 EventSource
EventSource
EventSource 是服務(wù)器推送的一個網(wǎng)絡(luò)事件接口。一個 EventSource 實例會對 HTTP 服務(wù)開啟一個持久化的連接,以text/event-stream 格式發(fā)送事件,會一直保持開啟直到被要求關(guān)閉。
一旦連接開啟,來自服務(wù)端傳入的消息會以事件的形式分發(fā)至你代碼中。如果接收消息中有一個事件字段,觸發(fā)的事件與事件字段的值相同。如果沒有事件字段存在,則將觸發(fā)通用事件。
與 WebSockets,不同的是,服務(wù)端推送是單向的。數(shù)據(jù)信息被單向從服務(wù)端到客戶端分發(fā)。當(dāng)不需要以消息形式將數(shù)據(jù)從客戶端發(fā)送到服務(wù)器時,這使它們成為絕佳的選擇。例如,對于處理社交媒體狀態(tài)更新,新聞提要或?qū)?shù)據(jù)傳遞到客戶端存儲機制(如 IndexedDB 或 Web 存儲)之類的,EventSource 無疑是一個有效方案。
— 引自 MDN
對比 WebSocket,它就是簡單,方便,在特定的一些場景下,比如聊天消息或市場價格,這就是 EventSource 擅長的
使用方式
它的使用方式極其簡單
const evtSource = new EventSource('sse.php');
const eventList = document.querySelector('ul');
evtSource.onmessage = function(e) {
let newElement = document.createElement("li");
newElement.textContent = "message: " + e.data;
eventList.appendChild(newElement);
}
對吧,幾行代碼搞定,如何攜帶參數(shù),在 new EventSource('sse.php?id=123');
其中 id=123
,就是我們要給鏈接傳的參數(shù)
問題來了
當(dāng)我實現(xiàn)之后,發(fā)現(xiàn)它在不斷的自動重連?搜了很多文檔,想不通,為何會自動重連,這里伏筆。想不通,ok,我就換個思路,改用 Axios 實現(xiàn)
axios
axios 實現(xiàn)如下
const streamToString = async (readableStream) => {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data);
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks).toString('base64'))
});
readableStream.on("error", reject);
});
}
axios({
method: 'get',
url:`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'stream'
}).then(async res => {
const raw = await streamToString(res.data);
})
此時還不知問題的**嚴(yán)重性!**實現(xiàn)完之后,發(fā)現(xiàn)不對勁啊,readableStream.on is not a fucntion
,???(黑人問號臉),遂打印 log 看看輸出的 res.data
是啥,字符串?根本不是一個方法啊,但看網(wǎng)上實現(xiàn),是這樣啊,沒錯?又看了幾遍,都是這樣實現(xiàn)的,很懵,直到看了下 axios 的 issue,傳送門,2016年就有人提出了這個問題,也就是說 axios 在瀏覽器側(cè)一直沒有實現(xiàn) steram,我內(nèi)心cnm,網(wǎng)上的文檔都是假的?。?!
也就是說,按照目前MDN說法,responseType
支持的類型有,arraybuffer、blob、document、json、text、ms-stream
,其中 ms-stream
,此響應(yīng)類型僅允許用于下載請求,并且僅受 Internet Explorer 支持
坑坑坑,又要開始了其他方案,想想 Fetch 能不能行,瀏覽器原生支持哦!
Fetch
Fetch API 提供了一個 JavaScript 接口,用于訪問和操縱 HTTP 管道的一些具體部分,例如請求和響應(yīng)。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網(wǎng)絡(luò)異步獲取資源。
這種功能以前是使用 XMLHttpRequest 實現(xiàn)的。Fetch 提供了一個更理想的替代方案,可以很容易地被其他技術(shù)使用,例如 Service Workers。Fetch 還提供了專門的邏輯空間來定義其他與 HTTP 相關(guān)的概念,例如 CORS 和 HTTP 的擴展。
— 引自 MDN
利用 Fetch 實現(xiàn)了如下代碼
const response = await fetch(`//xxx/api/chat/stream?prompt=${textarea.current.value.trim()}`);
const reader = response.body.getReader();
const eventList = document.querySelector('ul');
while (true) {
const { value, done } = await reader.read();
const utf8Decoder = new TextDecoder('utf-8');
let data: any = value ? utf8Decoder.decode(value, {stream: true}) : '';
try {
data = JSON.parse(data)
if (data.id || !data.content) {
return
}
let newElement = document.createElement("li");
newElement.textContent = "message: " + data.content;
eventList.appendChild(newElement);
} catch (e) {
}
if (done) {
break;
}
}
實現(xiàn)沒有問題,在我電腦上也跑通了,能穩(wěn)定接收服務(wù)端消息,不會自動重連,萬事大吉,轉(zhuǎn)交朋友試用
。。。。
交給朋友試用,反饋說,會出現(xiàn)回復(fù)不全???,調(diào)試搞起
瀏覽器側(cè)接收的消息
抓包看的消息
對比看,瀏覽器側(cè)**丟包!丟包了?。。?*幾番排查下來,不知為何會丟包,而且是只有 Windows 上會丟包(必現(xiàn)),macOS 上不會,不懂了呀,我們自己測試 Win 下 ping 都是穩(wěn)定的,有懂的同學(xué),可以告知下,謝謝!
最終解決方案
又回到 EventSource,沒錯,又回來了,折騰下來發(fā)現(xiàn),每次收完消息,你必須手動關(guān)閉下,evtSource.close();
,才不會自動重連,而且自動重連就是 EventSource 的特性之一,害,伏筆解決了。這個關(guān)閉有個前提是,服務(wù)端下發(fā)字段告訴你,能關(guān)閉,你才能關(guān)閉哦,折騰啊?。?!
總結(jié)
通過這次的學(xué)習(xí),讓我對 EventSource 以及 Fetch、Axios 有了一次深刻的認知,大家看完覺得還不錯的話,歡迎點贊,收藏哦
文章同步更新平臺:掘金、CSDN、知乎、思否、博客,公眾號(野生程序猿江辰)
我的聯(lián)系方式,v:Jiang9684,歡迎和我一起學(xué)習(xí)交流
完文章來源:http://www.zghlxwxcb.cn/news/detail-692081.html
本文由mdnice多平臺發(fā)布文章來源地址http://www.zghlxwxcb.cn/news/detail-692081.html
到了這里,關(guān)于EventSource 引發(fā)的一系列事件的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!