p>想在小程序里实现服务端主动推送消息,比如订单状态实时更新、聊天通知秒级触达,EventSource(基于SSE协议)是个轻量又好用的方案,但小程序环境和浏览器不一样,怎么上手?有哪些坑?这篇从基础到实战,把用法、优缺点、场景全拆明白。
EventSource 是基于 SSE(Server-Sent Events)协议 的前端技术,让服务端能主动给前端发消息,浏览器里直接用 new EventSource(url)
就能建立长连接,自动解析服务端发的 SSE 格式数据。
但小程序没有原生 EventSource
对象!得靠 wx.request
的 stream
响应模式 模拟实现,简单说:小程序里要自己写逻辑——发起长连接请求、拼接+解析 SSE 格式数据、处理重连,而浏览器把这些工作都封装好了。
举个直观对比:
浏览器里:
const sse = new EventSource('https://xxx/sse'); sse.onmessage = (e) => { console.log(e.data) };
小程序里:
得用wx.request
发请求,把responseType
设为stream
,监听onChunkReceived
事件,自己拼接数据、解析 SSE 格式(data: xxx\n\n
这种结构)。
小程序里怎么实现EventSource功能?(前端+后端步骤)
实现核心是 “前端发长连接请求+解析数据,后端按SSE协议发响应” ,分步骤拆解:
▶ 前端:请求、解析、重连三步走
发起长连接请求
用wx.request
,关键配置responseType: 'stream'
(让微信把响应拆成chunk
逐段推送),并监听onChunkReceived
事件。代码片段:
wx.request({ url: 'https://你的后端接口/sse', method: 'GET', // SSE仅支持GET请求 responseType: 'stream', success(res) { // res 是 stream 对象,监听每次收到的 chunk res.onChunkReceived(({ chunk }) => { console.log('收到数据块:', chunk); }); }, fail(err) { console.error('连接失败', err); } });
解析SSE格式数据
SSE 消息格式是 “每行以data:
/event:
/id:
开头,以\n\n 。
data: 你有新消息\n\n event: notify\ndata: {"msg": "有人@你"}\nid: 123\n\n
前端要做:
封装解析逻辑(工具函数示例):
function parseSSE(chunk, buffer) { buffer = (buffer || '') + chunk; // 拼接历史数据 const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 剩余未处理的部分,下次继续拼 const messages = []; lines.forEach(line => { if (line.startsWith('data:')) { const data = line.slice(5).trim(); // 提取 data 内容 messages.push({ type: 'message', data }); } else if (line.startsWith('event:')) { // 处理事件类型(比如区分“订单通知”和“聊天通知”) } else if (line.startsWith('id:')) { // 记录消息ID,重连时用 } }); return { messages, buffer }; }
结合请求逻辑使用:
let buffer = ''; res.onChunkReceived(({ chunk }) => { const { messages, buffer: newBuffer } = parseSSE(chunk, buffer); buffer = newBuffer; messages.forEach(msg => { // 触发自定义事件,让页面监听并更新UI this.triggerEvent('sseMessage', msg.data); }); });
把收到的
chunk
拼接成buffer
(防止一条消息被拆分到多个chunk
);按换行符分割
buffer
,提取data
/event
/id
等字段;重连逻辑:断了自动连
长连接容易因网络波动、服务端超时断开,必须做重连:封装重连逻辑(工具函数示例):
function createSSE(url, onMessage, onError) { let buffer = ''; let lastId = ''; // 记录服务端发的消息ID const task = wx.request({ url, method: 'GET', responseType: 'stream', header: { 'Last-Event-ID': lastId }, // 重连时带上次的ID success(res) { res.onChunkReceived(({ chunk }) => { const { messages, buffer: newBuffer } = parseSSE(chunk, buffer); buffer = newBuffer; messages.forEach(msg => { if (msg.id) lastId = msg.id; // 更新lastId onMessage(msg.data); // 把消息传给业务逻辑 }); }); }, fail(err) { onError(err); // 延迟1秒重试(可根据场景调整间隔) setTimeout(() => createSSE(url, onMessage, onError), 1000); } }); return () => task.abort(); // 返回“关闭连接”的函数 }
请求失败时(
fail
回调),延迟几秒重试;记录最后一次
id
(服务端发的id
字段),重连时带Last-Event-ID
请求头,让服务端“续传”消息(避免漏消息)。
▶ 后端:配置响应头+发SSE格式数据
设置响应头(关键!否则前端不认)
必须配置这三个头:Node.js + Express 示例:
const express = require('express'); const app = express(); app.get('/sse', (req, res) => { res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 后续发数据... });
Content-Type: text/event-stream
(告诉前端这是 SSE 数据);Cache-Control: no-cache
(关闭缓存,确保实时性);Connection: keep-alive
(保持HTTP长连接)。按SSE格式发数据
每个消息至少包含data
字段,并用\n\n
示例:简单消息:
res.write('data: 你有新订单\n\n');
带事件类型和ID:
res.write(`event: orderStatus\ndata: {"status": "已接单"}\nid: 123\n\n`);
保持长连接+处理断开
服务端要维持连接(不能主动关闭),可通过定时器发“心跳”(比如每隔几秒发个注释: heartbeat\n\n
),或等业务事件触发时发消息。要处理客户端断开(比如用户退出小程序),及时释放资源:
req.on('close', () => { clearInterval(timer); // 清除定时器 res.end(); // 关闭响应 });
EventSource在小程序里的优点,为啥选它不选WebSocket?
很多人纠结 SSE(EventSource)和 WebSocket,核心看场景,先看 SSE 的优势:
协议轻量,和小程序“合拍”
基于 HTTP 协议,用
https://
域名,和小程序普通接口的域名配置规则一致(不需要额外配置 WebSocket 的wss://
域名,少踩坑);只做服务端→前端的单向推送,不用处理 WebSocket 的“双工通信”(前端→服务端也能发消息),资源更省。
开发成本低
服务端只需按 SSE 格式发字符串,前端解析逻辑简单(对比 WebSocket 要处理“心跳包、消息分包、协议帧”);
浏览器
EventSource
自带自动重连(小程序里自己封装也不难),服务端还能通过retry
指令指定重连时间。适合单向高频推送
比如订单状态、实时通知,服务端频繁发消息时,SSE 的长连接比“轮询(定时发请求问有没有新消息)”高效太多,还能减少服务器压力。
哪些场景适合小程序用EventSource?
这些场景用 SSE,体验和开发效率都能拉满:
订单/物流状态实时更新
电商、外卖、快递小程序中,用户下单后,后端一有状态变化(支付成功、商家接单、快递到哪了),立刻推给前端,不用用户狂点刷新。即时通讯通知
聊天、社交小程序里,新消息提醒、好友申请、@通知等,服务端感知到事件就推送,用户秒级收到。实时数据展示
比如运动APP的步数同步、股票行情波动、IoT设备状态(家里的智能灯泡是否在线、温湿度多少),后端实时采集数据,前端实时更新UI。后台任务进度反馈
用户发起耗时操作(比如批量上传照片、导出Excel),后端推送进度(30%→50%→100%),前端显示进度条,用户不用干等。
小程序用EventSource会遇到哪些坑?怎么解决?
提前避坑少掉头发,总结常见问题和解法:
基础库版本限制
wx.request
的stream
响应类型,需要小程序基础库 10.0 及以上,如果要兼容低版本,只能降级用“轮询”(定时发请求查状态),但体验较差,新项目建议优先用高基础库。数据格式必须严格
服务端没按 SSE 格式发数据(比如少写data:
前缀、换行符不对),前端解析会乱套。后端必须严格遵循格式:每行以data:
/event:
/id:
开头,消息以\n\n
长连接容易断,重连要做好
网络波动、服务端超时都会断连接,解法:前端记录
lastId
,重连时带Last-Event-ID
请求头,让服务端“续传”消息(避免漏消息);服务端定时发“心跳”(比如每隔几秒发
: heartbeat\n\n
),维持连接;前端在
fail
回调里加延迟重试(比如隔 1 秒、3 秒、5 秒梯度重试,避免狂连)。跨域和域名配置
小程序的request
合法域名必须包含后端接口域名,如果是第三方接口,要么对方支持 CORS,要么把域名加到小程序的合法域名列表里,用云开发的话,域名配置更简单。消息太多处理不过来
服务端发消息太频繁,前端渲染跟不上,解法:服务端限制推送频率(比如每秒最多推 1 次);
前端用节流函数处理消息(比如合并 3 秒内的消息,一次性更新 UI)。
实战案例:小程序实时订单通知
结合前面的知识点,做个简化版案例,看代码怎么跑通。
▶ 需求
用户进入订单页,自动连接 SSE,后端推送订单状态(已接单→配送中→已送达),前端实时显示。
▶ 后端(Node.js + Express)
模拟订单状态变化,每 5 秒发一次状态:
const express = require('express'); const app = express(); app.get('/orderSSE', (req, res) => { res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 模拟订单状态:已接单→配送中→已送达 const statusList = ['已接单', '配送中', '已送达']; let index = 0; const timer = setInterval(() => { if (index < statusList.length) { const status = statusList[index]; // 发数据:带 event 和 data res.write(`event: orderStatus\ndata: {"status": "${status}"} \n\n`); index++; } else { clearInterval(timer); res.end(); } }, 5000); // 每5秒发一次状态 // 客户端断开时,清理定时器 req.on('close', () => { clearInterval(timer); res.end(); }); }); app.listen(3000, () => console.log('服务启动在3000端口'));
▶ 前端(小程序页面)
封装SSE工具函数(处理请求、解析、重连);
页面加载时发起连接,状态变化时更新 UI;
页面卸载时关闭连接,避免资源浪费。
代码示例:
// sseUtil.js(工具文件) function createOrderSSE(onStatus, onError) { let buffer = ''; let lastId = ''; const url = 'https://你的后端域名/orderSSE'; // 替换成自己的域名 const task = wx.request({ url, method: 'GET', responseType: 'stream', header: { 'Last-Event-ID': lastId }, success(res) { res.onChunkReceived(({ chunk }) => { // 解析SSE数据 const lines = (buffer + chunk).split('\n'); buffer = lines.pop() || ''; // 剩余未处理的部分 lines.forEach(line => { if (line.startsWith('data:')) { const dataStr = line.slice(5).trim(); const data = JSON.parse(dataStr); onStatus(data.status); // 把状态传给页面 } else if (line.startsWith('id:')) { lastId = line.slice(3).trim(); // 更新lastId } }); }); }, fail(err) { onError(err); // 延迟1秒重试 setTimeout(() => createOrderSSE(onStatus, onError), 1000); } }); return () => task.abort(); // 返回关闭连接的函数 } // orderPage.js(页面逻辑) Page({ data: { orderStatus: '等待中' }, onLoad() { // 发起SSE连接 this.closeSSE = createOrderSSE( (status) => { this.setData({ orderStatus: status }); // 更新UI }, (err) => { console.error('SSE出错', err); } ); }, onUnload() { // 页面卸载时关闭连接 this.closeSSE && this.closeSSE(); } }); // orderPage.wxml(页面结构) <view class="status-box"> 订单状态:{{orderStatus}} </view>
▶ 关键点讲解
buffer
处理:把每次收到的chunk
拼接,防止一条消息被拆分到多个chunk
里;lastId
记录:重连时带这个 ID,服务端可以“续传”之后的消息(避免用户断网期间漏消息);页面卸载关闭连接:避免小程序后台还维持长连接,浪费手机流量和内存。
未来趋势:小程序EventSource会更简单吗?
现在靠 wx.request
模拟 SSE,开发还是有点麻烦,但微信生态在迭代,未来可能:
官方提供更原生的EventSource API:像浏览器一样
new EventSource(url)
就能用,减少封装成本;结合云开发的实时能力:比如云函数触发消息推送,开发者不用自己搭后端 SSE 服务;
性能优化:官方优化
stream
响应的处理逻辑,降低前端解析的复杂度。
所以现在学 SSE,不仅能解决当下的实时推送需求,未来迭代后迁移成本也低~
小程序里用 EventSource(SSE),核心是 “前端模拟长连接+解析 SSE 格式,后端按协议发数据” ,它适合单向实时推送场景,比 WebSocket 轻量,比轮询高效,虽然现在需要自己封装,但掌握后能解决很多实时交互的痛点,从基础概念到实战案例,把流程跑通后,再针对性优化重
网友回答文明上网理性发言 已有0人参与
发表评论: