Я пытаюсь получать события, отправленные сервером (SSE), в браузере. Мое приложение использует axios (v1.6.8) для вызовов API. Этот код (любезно предоставленный этим ответом) работает в узле:
const getStreamAxios = () => {
axios.get('https://my-domain.com/api/getStream', {
responseType: 'stream',
headers: {
'Accept': 'text/event-stream',
}
})
.then(response => {
console.info('axios got a response');
const stream = response.data;
stream.on('data', data => {
console.info(data.toString('utf8'));
});
})
.catch(e => {
console.error('got an error', e);
});
}
Но в браузере Promise
никогда не выполняется (т. е. я не получаю консольное сообщение «axios получил ответ»). Вместо этого я получаю это предупреждение в консоли:
Предоставленное значение «поток» не является допустимым значением перечисления типа XMLHttpRequestResponseType.
Проблема не в конечной точке API; Помимо проверки с помощью узла, я проверил, что он также работает с Apidog и EventSource
в браузере.
🤔 А знаете ли вы, что...
С помощью JavaScript можно создавать клиентские приложения для мобильных устройств с использованием фреймворков, таких как React Native и NativeScript.
Корень проблемы в том, что axios начиная с версии 1.6.8 использует XMLHttpRequest в браузере (см. документацию axios). Это означает, что вы можете использовать responseType: 'stream'
только в node.
Наконец, эта проблема была решена в 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.info('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.info(value);
}
})
// catch/etc.
}
Если вы действительно заинтересованы в более старой версии 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.info(data);
}
})
Обратите внимание: независимо от выбранного вами варианта вам все равно придется что-то писать для использования событий, отправленных сервером. Вот пример использования @server-sent-stream/web:
const getStreamAxios = () => {
// axios.get(...) etc.
.then(async (response) => {
console.info('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.info(value)
}
})
}
Если вы не хотите анализировать это самостоятельно, вы можете посмотреть собственный EventSource API . Если вам нужны дополнительные параметры, не предусмотренные EventSource
(например, установка дополнительных заголовков), вы можете взглянуть на такие параметры, как sse.js или эту расширенную реализацию EventSource.