EventSource API

EventSource 接口是 web 内容与服务器发送事件通信的接口。

一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream 格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close() 关闭。一旦连接开启,来自服务端传入的消息会以事件的形式分发至客户端中,如果接收消息中有一个 event 字段,触发的事件与 event 字段的值相同。如果不存在 event 字段,则将触发通用的 message 事件。

与 WebSocket 不同的是,服务器发送事件是单向的。数据消息只能从服务端发送到客户端(如用户的浏览器)。这使其成为不需要从客户端往服务器发送消息的情况下的最佳选择。

构造函数

EventSource()

创建一个新的 EventSource,用于从指定的 URL 接收服务器发送事件,可以选择开启凭据模式。

用法: cosnt pc = new EventSource(url, configuration);

url 表示远程资源的位置,configuration 为配置新连接提供选项,目前只有一个可选项 withCredentials,默认为 false,指示 CORS 是否应包含凭据。

实例属性

此接口从其父接口 EventTarget 继承属性。

  • EventSource.readyState 只读
    一个代表连接状态的数字。可能值是 CONNECTING(0)、OPEN(1)或 CLOSED(2)。

  • EventSource.url 只读
    一个表示事件源的 URL 字符串。

  • EventSource.withCredentials 只读
    一个布尔值,表示 EventSource 对象是否使用跨域资源共享(CORS)凭据来实例化(true),或者不使用(false,即默认值)。

实例方法

此接口从其父接口 EventTarget 继承方法。

  • EventSource.close()
    关闭连接(如果有),并将 readyState 属性设置为 CLOSED。如果连接已经关闭,则该方法不执行任何操作。

事件

  • error
    在事件源连接未能打开时触发。

  • message
    在从事件源接收到数据时触发。

  • open
    在与事件源的连接打开时触发。

此外,事件源本身可以发送具有 event 字段的消息,这将创建一个以该值为键的特定事件。

示例

在这个基本的例子中,创建了一个 EventSource 来从服务器接收未命名的事件;一个名为 sse.php 的页面负责生成这些事件。

1
2
3
4
5
6
7
8
9
10
const evtSource = new EventSource("sse.php?message=hello");
const eventList = document.querySelector("ul");

evtSource.onmessage = (e) => {
const newElement = document.createElement("li");

newElement.textContent = `message: ${e.data}`;
eventList.appendChild(newElement);
evtSource.close();
};

每个接收到的事件都会导致我们的 EventSource 对象的 onmessage 事件处理程序运行。它会创建一个新的 <li> 元素,并将消息的数据写入其中,然后将新元素附加到文档中已有的列表元素中。

要监听具名事件,你需要为每种类型的事件添加一个监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const sse = new EventSource("/api/v1/sse");

/*
* 这将仅监听类似下面的事件
*
* event: notice
* data: useful data
* id: someid
*/
sse.addEventListener("notice", (e) => {
console.log(e.data);
});

/*
* 同理,以下代码将监听具有字段 `event: update` 的事件
*/
sse.addEventListener("update", (e) => {
console.log(e.data);
});

/*
* “message”事件是一个特例,因为它可以捕获没有 event 字段的事件,
* 以及具有特定类型 `event:message` 的事件。
* 它不会触发任何其他类型的事件。
*/
sse.addEventListener("message", (e) => {
console.log(e.data);
});

注意

  1. 浏览器同域名下的HTTP请求数量是有限制的(例如Chrome浏览器的限制为6个),所以如果打开多个选项卡,这种长连接可能会因为占满限制名额而导致无法发送新的HTTP请求。

  2. EventSource API 发送的HTTP请求为GET类型的,如果想要发送POST类型的HTTP请求,或者修改HTTP请求的headers,可以使用 @microsoft/fetch-event-source 库,该库对这种情况进行了polyfill,使用方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { fetchEventSource } from '@microsoft/fetch-event-source';
    fetchEventSource(url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    Accept: '*/*',
    Connection: 'keep-alive',
    },
    body: JSON.stringify({
    messages: [{
    content: 'Hello.',
    role: 'user',
    }],
    }),
    onmessage(event) {
    console.log(`EventSource onmessage ${Date.now()}: ${event.data}`);
    },
    onerror() {
    console.log('EventSource onerror');
    },
    });

    类似的库还有 event-source-polyfill 库。

  3. 针对问答式的请求,服务器端返回的文本数据为流式时,EventSource API 相比 WebSocket 更加简单便捷,chatGPT 类问答服务多采用 EventSource API 来实现。