Как я могу использовать Axios с событиями, отправленными сервером (SSE) в браузере?

Я пытаюсь получать события, отправленные сервером (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.


1
1 784
1

Ответ:

Решено

Корень проблемы в том, что 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.