题意:如何在浏览器中使用 Axios 处理服务器发送事件(SSE)?
问题背景:
I'm trying to receive Server-sent events (SSE) in the browser. My application uses axios (v1.6.8) to make API calls. This code works in node:
我正在尝试在浏览器中接收服务器发送事件(SSE)。我的应用程序使用 axios(v1.6.8)进行 API 调用。以下代码可以在 Node.js 中运行:
const getStreamAxios = () => {
axios.get('https://my-domain.com/api/getStream', {
responseType: 'stream',
headers: {
'Accept': 'text/event-stream',
}
})
.then(response => {
console.log('axios got a response');
const stream = response.data;
stream.on('data', data => {
console.log(data.toString('utf8'));
});
})
.catch(e => {
console.error('got an error', e);
});
}
But in the browser, the Promise
is never fulfilled (i.e.: I don't get the console message "axios got a response"). Instead I get this warning in the console:
但是在浏览器中,Promise 永远不会被解决(即:我没有看到控制台信息 "axios got a response")。相反,我在控制台中收到了这个警告:
The provided value 'stream' is not a valid enum value of type XMLHttpRequestResponseType.
The issue is not in the API endpoint; in addition to checking it with node, I have verified it also works with Apidog and EventSource
in the browser.
问题不在于 API 端点;除了使用 Node.js 进行检查外,我还验证了它在 Apidog 和浏览器中的 `EventSource` 上也能正常工作。
问题解决:
The root of the problem is, axios as of v1.6.8, uses XMLHttpRequest in the browser (see axios docs). This means you can only use responseType: 'stream'
in node.
问题的根源在于,axios 从 v1.6.8 开始在浏览器中使用的是 `XMLHttpRequest`(参见 axios 文档)。这意味着你只能在 Node.js 中使用 `responseType: 'stream'`。
This was finally addressed in v1.7.0, which allows you to use fetch instead of XHR, like so:
这个问题最终在 v1.7.0 中得到了处理,该版本允许你使用 `fetch` 而不是 `XHR`,如下所示:
const getStreamAxios = () => {
axios.get('https://my-domain.com/api/getStream', {
headers: {
'Accept': 'text/event-stream',
},
responseType: 'stream',
adapter: 'fetch', // <- this option can also be set in axios.create()
})
.then(async (response) => {
console.log('axios got a response');
const stream = response.data;
// consume response
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log(value);
}
})
// catch/etc.
}
If you are really invested in an older version of axios, there is a dodgy-looking workaround, using the onDownloadProgress
option:
如果你非常依赖旧版本的 axios,可以使用一个看起来不太可靠的解决方法,即使用 `onDownloadProgress` 选项:
const getStreamAxios = () => {
axios.get('https://my-domain.com/api/getStream', {
headers: {
'Accept': 'text/event-stream',
},
onDownloadProgress: (evt) => {
// Parse response from evt.event.target.responseText || evt.event.target.response
// The target holds the accumulator + the current response, so basically everything from the beginning on each response
// Note that it's evt.target instead of evt.event.target for older axios versions
let data = evt.event.target.responseText;
console.log(data);
}
})
Caveat
Note that, regardless of the option you choose, you will still have to write something to consume the server-sent events. Here's an example using @server-sent-stream/web:
请注意,无论你选择哪种方案,你仍然需要编写代码来处理服务器发送的事件。以下是一个使用 `@server-sent-stream/web` 的示例:
const getStreamAxios = () => {
// axios.get(...) etc.
.then(async (response) => {
console.log('axios got a response');
const stream = response.data; // <- should be a ReadableStream
const decoder = new EventSourceStream();
stream.pipeThrough(decoder);
// Read from the EventSourceStream
const reader = decoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
// The value will be a `MessageEvent`.
// MessageEvent {data: 'message data', lastEventId: '', …}
console.log(value)
}
})
}
If you don't want to parse this yourself, you can look at the native EventSource API. If you need more options not provided by EventSource
(such as setting additional headers), you can take a look at options like sse.js, or this extended EventSource implementation
如果你不想自己解析这些事件,可以查看原生的 `EventSource` API。如果你需要 `EventSource` 未提供的更多选项(如设置额外的请求头),可以考虑 `sse.js` 或这个扩展版的 `EventSource` 实现。