×

EventSource在小程序里是什么?和浏览器有啥区别?

提问者:Terry2025.08.18浏览:28

p>想在小程序里实现服务端主动推送消息,比如订单状态实时更新、聊天通知秒级触达,EventSource(基于SSE协议)是个轻量又好用的方案,但小程序环境和浏览器不一样,怎么上手?有哪些坑?这篇从基础到实战,把用法、优缺点、场景全拆明白。

EventSource 是基于 SSE(Server-Sent Events)协议 的前端技术,让服务端能主动给前端发消息,浏览器里直接用 new EventSource(url) 就能建立长连接,自动解析服务端发的 SSE 格式数据。

但小程序没有原生 EventSource 对象!得靠 wx.requeststream 响应模式 模拟实现,简单说:小程序里要自己写逻辑——发起长连接请求、拼接+解析 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协议发响应” ,分步骤拆解:

▶ 前端:请求、解析、重连三步走

  1. 发起长连接请求
    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);
      }
    });
  2. 解析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 等字段;

  3. 重连逻辑:断了自动连
    长连接容易因网络波动、服务端超时断开,必须做重连:

    封装重连逻辑(工具函数示例):

    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格式数据

  1. 设置响应头(关键!否则前端不认)
    必须配置这三个头:

    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长连接)。

  2. 按SSE格式发数据
    每个消息至少包含 data 字段,并用 \n\n 示例:

    • 简单消息:res.write('data: 你有新订单\n\n');

    • 带事件类型和ID:  

      res.write(`event: orderStatus\ndata: {"status": "已接单"}\nid: 123\n\n`);
  3. 保持长连接+处理断开
    服务端要维持连接(不能主动关闭),可通过定时器发“心跳”(比如每隔几秒发个注释 : heartbeat\n\n),或等业务事件触发时发消息

    要处理客户端断开(比如用户退出小程序),及时释放资源:

    req.on('close', () => {
      clearInterval(timer); // 清除定时器
      res.end(); // 关闭响应
    });

EventSource在小程序里的优点,为啥选它不选WebSocket?

很多人纠结 SSE(EventSource)和 WebSocket,核心看场景,先看 SSE 的优势:

  1. 协议轻量,和小程序“合拍”

    • 基于 HTTP 协议,用 https:// 域名,和小程序普通接口的域名配置规则一致(不需要额外配置 WebSocket 的 wss:// 域名,少踩坑);

    • 只做服务端→前端的单向推送,不用处理 WebSocket 的“双工通信”(前端→服务端也能发消息),资源更省。

  2. 开发成本低

    • 服务端只需按 SSE 格式发字符串,前端解析逻辑简单(对比 WebSocket 要处理“心跳包、消息分包、协议帧”);

    • 浏览器 EventSource 自带自动重连(小程序里自己封装也不难),服务端还能通过 retry 指令指定重连时间。

  3. 适合单向高频推送
    比如订单状态、实时通知,服务端频繁发消息时,SSE 的长连接比“轮询(定时发请求问有没有新消息)”高效太多,还能减少服务器压力。

哪些场景适合小程序用EventSource?

这些场景用 SSE,体验和开发效率都能拉满:

  1. 订单/物流状态实时更新
    电商、外卖、快递小程序中,用户下单后,后端一有状态变化(支付成功、商家接单、快递到哪了),立刻推给前端,不用用户狂点刷新。

  2. 即时通讯通知
    聊天、社交小程序里,新消息提醒、好友申请、@通知等,服务端感知到事件就推送,用户秒级收到。

  3. 实时数据展示
    比如运动APP的步数同步、股票行情波动、IoT设备状态(家里的智能灯泡是否在线、温湿度多少),后端实时采集数据,前端实时更新UI。

  4. 后台任务进度反馈
    用户发起耗时操作(比如批量上传照片、导出Excel),后端推送进度(30%→50%→100%),前端显示进度条,用户不用干等。

小程序用EventSource会遇到哪些坑?怎么解决?

提前避坑少掉头发,总结常见问题和解法:

  1. 基础库版本限制
    wx.requeststream 响应类型,需要小程序基础库 10.0 及以上,如果要兼容低版本,只能降级用“轮询”(定时发请求查状态),但体验较差,新项目建议优先用高基础库。

  2. 数据格式必须严格
    服务端没按 SSE 格式发数据(比如少写 data: 前缀、换行符不对),前端解析会乱套。后端必须严格遵循格式:每行以 data:/event:/id: 开头,消息以 \n\n

  3. 长连接容易断,重连要做好
    网络波动、服务端超时都会断连接,解法:

    • 前端记录 lastId,重连时带 Last-Event-ID 请求头,让服务端“续传”消息(避免漏消息);

    • 服务端定时发“心跳”(比如每隔几秒发 : heartbeat\n\n),维持连接;

    • 前端在 fail 回调里加延迟重试(比如隔 1 秒、3 秒、5 秒梯度重试,避免狂连)。

  4. 跨域和域名配置
    小程序的 request 合法域名必须包含后端接口域名,如果是第三方接口,要么对方支持 CORS,要么把域名加到小程序的合法域名列表里,用云开发的话,域名配置更简单。

  5. 消息太多处理不过来
    服务端发消息太频繁,前端渲染跟不上,解法:

    • 服务端限制推送频率(比如每秒最多推 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端口'));

▶ 前端(小程序页面)

  1. 封装SSE工具函数(处理请求、解析、重连);

  2. 页面加载时发起连接,状态变化时更新 UI;

  3. 页面卸载时关闭连接,避免资源浪费。

代码示例:

// 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,开发还是有点麻烦,但微信生态在迭代,未来可能:

  1. 官方提供更原生的EventSource API:像浏览器一样 new EventSource(url) 就能用,减少封装成本;

  2. 结合云开发的实时能力:比如云函数触发消息推送,开发者不用自己搭后端 SSE 服务;

  3. 性能优化:官方优化 stream 响应的处理逻辑,降低前端解析的复杂度。

所以现在学 SSE,不仅能解决当下的实时推送需求,未来迭代后迁移成本也低~

小程序里用 EventSource(SSE),核心是 “前端模拟长连接+解析 SSE 格式,后端按协议发数据” ,它适合单向实时推送场景,比 WebSocket 轻量,比轮询高效,虽然现在需要自己封装,但掌握后能解决很多实时交互的痛点,从基础概念到实战案例,把流程跑通后,再针对性优化重

您的支持是我们创作的动力!

网友回答文明上网理性发言 已有0人参与

发表评论: