跳到主要内容

@microsoft/fetch-event-source

本包提供了一个更高级的 API 来处理事件源请求,也称为服务器端发送事件(Server-Sent Events)。它兼容了 Fetch API 的所有特性。

默认的浏览器 EventSource API 对可进行的请求类型有诸多限制:

  • 不能传递请求体:必须将执行请求所需的所有信息编码在 URL 中,而大多数浏览器中 URL 的长度限制为 2000 个字符[1]。
  • 不能传递自定义请求头
  • 只能进行 GET 请求,无法指定其他方法。
  • 连接断开时,没有控制重试策略的权限:浏览器会静默地尝试几次重连,然后停止,这对于任何类型的稳健应用来说都不够。

本库提供了一种基于 Fetch API 的替代接口来消费服务器端发送事件。它完全兼容 事件流格式,因此如果您的服务器正在发出这些事件,您仍然可以像以前一样进行消费。然而,现在您可以更全面地控制请求和响应:

  • 可以使用任何请求方法、头信息或请求体,以及 Fetch API 提供的所有其他功能。甚至可以提供替代的 fetch() 实现,如果默认浏览器实现不适用于您。
  • 在解析事件源之前,您可以访问响应对象。这在您的应用服务器前有 API 网关(如 nginx)时很有用:如果网关返回错误,您可能希望正确处理它。
  • 如果连接断开或发生错误,您可以完全控制重试策略

此外,此库还集成到浏览器的 页面可见性 API 中,当文档被隐藏(例如,用户最小化窗口)时,连接会关闭。当它再次变得可见时,它会自动使用最后一个事件 ID 重试。这减少了服务器负载,因为不会无谓地保持打开的连接(但您可以选择不启用此行为)。

安装

npm install @microsoft/fetch-event-source

使用

// 之前:
const sse = new EventSource('/api/sse');
sse.onmessage = (ev) => {
console.log(ev.data);
};

// 之后:
import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('/api/sse', {
onmessage(ev) {
console.log(ev.data);
}
});

您可以传递所有默认 Fetch API 提供的其他参数,例如:

const ctrl = new AbortController();
fetchEventSource('/api/sse', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
foo: 'bar'
}),
signal: ctrl.signal,
});

您可以添加更好的错误处理,例如:

class RetriableError extends Error { }
class FatalError extends Error { }

fetchEventSource('/api/sse', {
async onopen(response) {
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
return; // everything's good
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
// client-side errors are usually non-retriable:
throw new FatalError();
} else {
throw new RetriableError();
}
},
onmessage(msg) {
// if the server emits an error message, throw an exception
// so it gets handled by the onerror callback below:
if (msg.event === 'FatalError') {
throw new FatalError(msg.data);
}
},
onclose() {
// if the server closes the connection unexpectedly, retry:
throw new RetriableError();
},
onerror(err) {
if (err instanceof FatalError) {
throw err; // rethrow to stop the operation
} else {
// do nothing to automatically retry. You can also
// return a specific retry interval here.
}
}
});

连接行为

@microsoft/fetch-event-source 这个库中,openWhenHidden 是一个可选的配置选项,它允许你控制当页面不可见(例如,用户最小化浏览器窗口或者切换到其他应用)时事件源连接的行为。

以下是 openWhenHidden 选项的一些关键点:

  1. 默认行为:在没有指定 openWhenHidden 时,大多数浏览器在页面变为不可见时会自动关闭事件源连接。这是为了节省服务器资源,因为在用户不再查看页面时,维持一个打开的连接通常是没有必要的。

  2. 开启此功能:通过设置 openWhenHiddentrue(默认为 false),你可以让事件源即使在页面不可见时也保持连接打开。这可以让你在页面重新变为可见时接收最新的事件。

  3. 适用场景

    • 如果你有一个需要持续监控的实时数据流,并且用户可能会最小化或切换到其他应用,那么开启 openWhenHidden 可能是有用的。
    • 例如,一个聊天应用程序可能需要在用户离开聊天窗口时继续接收消息。
  4. 注意事项

    • 开启此功能会增加服务器的负载,因为它需要维持更多的连接打开状态。
    • 在页面不可见时保持连接可能会对用户的设备性能产生影响,因为浏览器会尝试在后台处理这些事件。
  5. 配置选项:除了 openWhenHidden,你还可以通过以下选项进一步控制行为:

    • reconnectInterval:指定在连接断开时自动重连的间隔时间(以毫秒为单位)。
    • maxReconnectInterval:指定重连尝试的最大间隔时间。

下面是一个配置示例:

import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('/api/sse', {
onmessage(ev) {
console.log(ev.data);
},
openWhenHidden: true, // 保持连接在页面不可见时打开
reconnectInterval: 5000, // 设置重连间隔为5秒
});

在这个例子中,即使页面变为不可见,事件源连接也会保持打开状态,并且在连接断开后每5秒钟尝试重新连接。

可能的问题

提示
  1. 在开发或代理服务器环境下,有时可能遇到无输出流的状况(最后返回全部结果)。为了确保数据的顺畅传输,特别是在使用代理服务器时,有必要启用基于服务器端事件(Server-Sent Events, SSE)的代理模式。
  2. 当发现连接错误时期望连接中断,请在onerror里直接抛出异常。

兼容性

此库是用 TypeScript 编写的,并针对所有绿色浏览器(Chrome、Firefox、Safari、Edge)支持的 ES2017 特性。您可能需要为旧版 Edge(版本 < 79)使用 TextDecoder 的 polyfill:

require('fast-text-encoding');

贡献

本项目欢迎贡献和建议。大多数贡献都需要您同意一个贡献者许可协议 (CLA),声明您有权利,并且实际上确实授予我们使用您的贡献的权利。有关详细信息,请访问 https://cla.opensource.microsoft.com。

当您提交拉取请求时,CLA 机器人将自动确定您是否需要提供 CLA 并相应地装饰 PR(例如,状态检查、评论)。只需遵循机器人的说明即可。您可能只需要这样做一次,在所有使用我们 CLA 的存储库中。

本项目采用了 Microsoft 开源行为准则。有关更多信息,请参阅行为准则常见问题解答或联系 opencode@microsoft.com 以获取任何额外的问题或评论。