uniapp处理流式请求

在uniapp里面处理流式请求相对于web端来说有点麻烦,下面我将讲述几种处理流式请求的方式。

1.websocket

WebSocket 是处理实时数据流的最佳选择之一,UniApp 提供了原生的 WebSocket 支持:

<template><view class="container"><scroll-view scroll-y class="data-container" :scroll-top="scrollTop"><view v-for="(item, index) in messages" :key="index" class="message-item">{{ item }}</view></scroll-view><view class="control-panel"><button @click="connectSocket" type="primary" :disabled="isConnected">连接</button><button @click="closeSocket" type="warn" :disabled="!isConnected">断开</button></view></view>
</template><script>
export default {data() {return {socketTask: null,isConnected: false,messages: [],scrollTop: 0}},methods: {connectSocket() {this.socketTask = uni.connectSocket({url: 'wss://your-websocket-server.com/stream',success: () => {console.log('准备连接...')}})this.socketTask.onOpen(() => {this.isConnected = truethis.addMessage('连接已建立')// 订阅数据流this.socketTask.send({data: JSON.stringify({action: 'subscribe'})})})this.socketTask.onMessage((res) => {this.addMessage(res.data)})this.socketTask.onClose(() => {this.isConnected = falsethis.addMessage('连接已关闭')})this.socketTask.onError((res) => {this.addMessage('错误: ' + JSON.stringify(res))})},closeSocket() {if (this.socketTask && this.isConnected) {this.socketTask.close()}},addMessage(msg) {this.messages.push(typeof msg === 'string' ? msg : JSON.stringify(msg))this.$nextTick(() => {this.scrollTop = 99999 // 滚动到底部})}},onUnload() {// 页面卸载时关闭连接this.closeSocket()}
}
</script><style>
.container {display: flex;flex-direction: column;height: 100vh;padding: 20rpx;
}
.data-container {flex: 1;border: 1px solid #eee;padding: 20rpx;margin-bottom: 20rpx;background-color: #f9f9f9;
}
.message-item {padding: 10rpx;border-bottom: 1px solid #eee;word-break: break-all;
}
.control-panel {display: flex;justify-content: space-around;padding: 20rpx 0;
}
</style>

2.uni.request 处理流式请求

这种方式适用于h5页面和小程序,不适用于app

<template><view class="container"><view class="stream-container"><view v-for="(item, index) in streamData" :key="index" class="stream-item">{{ item }}</view></view><button @click="startStream" type="primary">开始接收流数据</button></view>
</template><script>
export default {data() {return {streamData: [],dataBuffer: ''}},methods: {startStream() {uni.request({url: 'https://your-stream-api.com/stream',method: 'GET',enableChunked: true,dataType: 'text',onChunkReceived: (res) => {this.handleChunk(res.data);},success: (res) => {uni.showToast({title: '流数据接收完成',icon: 'success'});},fail: (err) => {uni.showModal({title: '错误',content: '流数据接收失败: ' + JSON.stringify(err),showCancel: false});}});},handleChunk(chunk) {// 将接收到的数据添加到缓冲区this.dataBuffer += chunk;// 处理可能的换行符分隔的数据const lines = this.dataBuffer.split('\n');// 保留最后一个可能不完整的行this.dataBuffer = lines.pop() || '';// 处理完整的行for (const line of lines) {if (line.trim()) {try {// 尝试解析JSONconst data = JSON.parse(line);this.streamData.push(JSON.stringify(data));} catch (e) {// 非JSON数据直接显示this.streamData.push(line);}}}}}
}
</script><style>
.container {padding: 20px;
}
.stream-container {border: 1px solid #eee;padding: 10px;margin-bottom: 20px;max-height: 300px;overflow-y: auto;
}
.stream-item {padding: 5px 0;border-bottom: 1px solid #f5f5f5;
}
</style>

3.使用 SSE (Server-Sent Events)

虽然 UniApp 没有原生的 SSE API,但可以通过封装 XMLHttpRequest 来实现 SSE:

function createSSEConnection(url) {// 创建一个标准的XMLHttpRequest对象const xhr = new XMLHttpRequest()xhr.open('GET', url, true)xhr.setRequestHeader('Accept', 'text/event-stream')xhr.setRequestHeader('Cache-Control', 'no-cache')// 设置响应类型为文本xhr.responseType = 'text'// 数据缓冲区let buffer = ''// 处理进度事件xhr.onprogress = function(e) {// 获取新数据const newData = xhr.responseText.substring(buffer.length)if (newData) {buffer += newData// 按行分割数据const lines = newData.split('\n')for (const line of lines) {if (line.startsWith('data:')) {const eventData = line.substring(5).trim()// 触发数据处理handleSSEData(eventData)}}}}xhr.onerror = function(e) {console.error('SSE连接错误:', e)}xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {console.log('SSE连接完成')} else {console.error('SSE连接失败:', xhr.status)}}}// 发送请求xhr.send()return xhr
}// 处理SSE数据
function handleSSEData(data) {try {const parsedData = JSON.parse(data)console.log('收到SSE数据:', parsedData)// 处理数据...} catch (e) {console.log('收到SSE文本:', data)// 处理非JSON数据...}
}// 使用方法
const sseConnection = createSSEConnection('https://your-sse-endpoint.com/events')// 关闭连接
function closeSSE() {if (sseConnection) {sseConnection.abort()}
}

4.使用分页和轮询模拟流

对于不支持真正的流式请求的场景,可以使用分页和轮询来模拟流式体验:

// 轮询获取数据
let lastId = 0
let isPolling = falsefunction startPolling() {isPolling = truepoll()
}function stopPolling() {isPolling = false
}function poll() {if (!isPolling) returnuni.request({url: 'https://your-api.com/data',data: {last_id: lastId,limit: 10},success: (res) => {const data = res.dataif (data && data.items && data.items.length > 0) {// 处理接收到的数据processItems(data.items)// 更新最后ID用于下次请求lastId = data.items[data.items.length - 1].id}// 如果还有更多数据,继续轮询if (data.has_more) {setTimeout(poll, 1000) // 1秒后再次轮询} else {console.log('所有数据接收完毕')isPolling = false}},fail: (err) => {console.error('轮询失败:', err)// 错误后延迟重试setTimeout(() => {if (isPolling) poll()}, 3000)}})
}

5.app端适用renderjs

该方式适用于app端进行流式请求,在app端上面的几种方式我都尝试过除了websocket没有一个能用的,经过反复的查询才找到适用renderjs这种方式。

RenderJS 是 UniApp 提供的一个运行在视图层的 JavaScript 引擎,允许开发者直接操作 DOM 和使用浏览器特有的 API。

<viewclass="":sseValue="sseValue":change:sseValue="renderScript.getSseValue":messagesRenderjs="messagesRenderjs":change:messagesRenderjs="renderScript.getMessage":downSend="downSend":change:downSend="renderScript.getDownSend":modelchangeValue="modelchangeValue":change:modelchangeValue="renderScript.getModel"></view>
通过这种方式来调用renderjs
<script module="renderScript" lang="renderjs">
export default {data() {return {VITE_AIR14B_url: '',VITE_AILOCAL:'',VITE_ANYTHING:'',modelValue: 'qwen2.5:14b',messages: [],downValue: false,loading: false,downSentValue: false,biaoshi: false,model:'qianwen'};},methods: {getMessage(val) {// console.log(val,'message')this.messages = val;},async getSseValue(val) {console.log(val,'val')if(this.model=='qianwen'){await this.changeModel(val);}else if(this.model=='locel'){await this.changeModelLocal(val)}else if(this.model=='water'){await this.changeModelAnything(val)}else if(this.model=='sxyd'){console.log('sxyd')}},// 暂停生成getDownSend(val) {console.log(val, 'valvalval');if (!this.biaoshi) {this.biaoshi = true;} else {this.downSentValue = true;}//停止生成的标志// this.downSentValue=true},// 获取模型getModel(val) {console.log(val);this.model=val},// 接收流式数据qianwenasync changeModel(val) {let params = {model: this.modelValue,messages: this.messages,stream: true};try {// 发送请求到 APIconst response = await fetch(this.VITE_AIR14B_url + '/api/chat', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(params)});// 获取流式响应体const reader = response.body.getReader();const decoder = new TextDecoder('utf-8'); // 解码器,将字节流转换为字符串this.downValue = false;let value = ''; // 用于拼接收到的每一部分 response 内容// 获取响应之后关闭loading// this.$ownerInstance.callMethod('closeLoading', null);this.messages.push({role: 'assistant',content: ''});// 逐块读取数据while (!this.downValue) {// 读取数据块const { done: isDone, value: chunk } = await reader.read();// if (this.onDown.value) {//     this.downValue = true;//     break;// }if (this.downSentValue) {this.downValue = true;this.downSentValue = false;this.$ownerInstance.callMethod('setSend', true);this.messages[this.messages.length - 1].content += '\n\n[已取消生成]';this.$ownerInstance.callMethod('setMessageValue', this.messages);break;}this.downValue = isDone;if (this.downValue) {this.$ownerInstance.callMethod('setSend', true);} else {this.$ownerInstance.callMethod('setSend', false);}let chunkString = decoder.decode(chunk, { stream: true });// 将字节流转换为字符串并追加到 valuevalue += chunkString;// 打印已接收的部分数据// console.log('Received chunk:', chunkString);if (chunkString && this.messages[this.messages.length - 1].role === 'assistant') {try {const parsedChunk = JSON.parse(chunkString);const content = parsedChunk?.message?.content; // 使用可选链避免属性访问错误if (content === undefined) {throw new Error('Invalid chunk structure: Missing message.content');}// 确保 messages 数组非空if (this.messages.length === 0) {throw new Error('Messages array is empty');}this.messages[this.messages.length - 1].content += content;if(this.messages[this.messages.length - 1].content){// 获取响应之后关闭loadingthis.$ownerInstance.callMethod('closeLoading', null);}// 将值传递出去this.$ownerInstance.callMethod('setMessageValue', this.messages);} catch (error) {console.error('处理 chunk 时发生错误:', error);}}}// console.log('Final response:', value);} catch (error) {console.error('Error during fetch request:', error);this.loading = false;// 显示错误消息if (this.messages.length > 0 && this.messages[this.messages.length - 1].role === 'assistant') {this.messages[this.messages.length - 1].content += '\n\n[生成回答时出现错误]';}}},}
};
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/74479.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

低代码理解

一、低代码开发的核心定义 低代码开发是通过可视化界面和声明式编程替代传统手工编码的软件开发范式&#xff0c;其核心目标是&#xff1a; 降低技术门槛&#xff1a;允许非专业开发者&#xff08;公民开发者&#xff09;参与应用构建提升开发效率&#xff1a;通过复用预制组…

WHAM 人体3d重建部署笔记 vitpose

目录 视频结果: docker安装说明: conda环境安装说明: 依赖项: 依赖库: 安装 mmpose,mmcv 下载模型权重: 算法原理, demo脚本 报错inference_top_down_pose_model: 测试命令: 视频结果: wham_smpl预测结果 git地址: GitHub - yohanshin/WHAM WHAM: Recons…

react 大屏根据屏幕分辨率缩放

记录&#xff0c;以防忘记 const DataLargeScreen () > {const layoutRef useRef<any>();// ui稿宽度const width useRef(1920).current;// ui稿高度const height useRef(1080).current;const [scaleValue, setScaleValue] useState(1);const useWhichScaleValu…

【网络】网关

【网络】网关 网关 是计算机网络中用于连接两个不同网络的设备或服务器&#xff0c;它充当着“翻译器”和“转发器”的角色&#xff0c;将数据包从一个网络传递到另一个网络&#xff0c;并在必要时进行协议转换和数据重包装。 主要功能 数据转发&#xff1a;当本地网络设备发…

Axure大屏可视化模板:赋能多领域,开启数据展示新篇章

在当今这个数据爆炸的时代&#xff0c;数据已经成为各行各业的核心资产。然而&#xff0c;如何高效、直观地展示数据&#xff0c;并将其转化为有价值的决策依据&#xff0c;成为了许多企业和组织面临的共同挑战。Axure大屏可视化模板&#xff0c;作为一款强大的数据展示工具&am…

数据不外传!通过内网穿透实现绿联NAS远程访问的安全配置方案

文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 大家好&#xff0c;今天要带给大家一个超级酷炫的技能——如何让绿联NAS秒变‘千里眼’&#xff0c;通过简单的几步操作就能轻松实现内网穿透。想象一下&#xff0c;无论你身处何地&a…

面试题精选《剑指Offer》:JVM类加载机制与Spring设计哲学深度剖析-大厂必考

一、JVM类加载核心机制 &#x1f525; 问题5&#xff1a;类从编译到执行的全链路过程 完整生命周期流程图 关键技术拆解 编译阶段 查看字节码指令&#xff1a;javap -v Robot.class 常量池结构解析&#xff08;CONSTANT_Class_info等&#xff09; 类加载阶段 // 手动加载…

WordPress分类目录绑定二级域名插件

一.子域名访问形式 1.wordpress 分类目录 转换为 子域名 &#xff08;绑定二级域名&#xff09;形式 2.wordpress 页面转换为 子域名 &#xff08;绑定二级域名&#xff09; 形式 3.wordpress 作者页转换为 子域名 &#xff08;绑定二级域名&#xff09;形式 4.为不同子域名…

Shopify Checkout UI Extensions

结账界面的UI扩展允许应用开发者构建自定义功能&#xff0c;商家可以在结账流程的定义点安装&#xff0c;包括产品信息、运输、支付、订单摘要和Shop Pay。 Shopify官方在去年2024年使用结账扩展取代了checkout.liquid&#xff0c;并将于2025年8月28日彻底停用checkout.liquid…

华为HCIE方向那么多应该如何选择?

在华为认证体系里&#xff0c;HCIE作为最高等级的认证&#xff0c;是ICT领域专业实力的有力象征。HCIE设置了多个细分方向&#xff0c;这些方向宛如不同的专业赛道&#xff0c;为期望在ICT行业深入发展的人提供了丰富的选择。今天&#xff0c;咱们就来好好聊聊华为HCIE方向的相…

bootstrap介绍(前端框架)(提供超过40种可复用组件,从导航栏到轮播图,从卡片到弹窗)bootstrap框架

文章目录 Bootstrap框架全解析起源与发展核心特性与优势响应式设计组件丰富度一致性与兼容性 栅格系统深度解析栅格系统工作原理断点设置与响应式策略 组件系统导航组件表单系统 自定义与扩展SASS变量系统构建系统优化 性能优化策略按需加载减少嵌套层级 实践案例&#xff1a;电…

FastGPT原理分析-数据集创建第二步:处理任务的执行

概述 文章《FastGPT原理分析-数据集创建第一步》已经分析了数据集创建的第一步&#xff1a;文件上传和预处理的实现逻辑。本文介绍文件上传后&#xff0c;数据处理任务的具体实现逻辑。 数据集创建总体实现步骤 从上文可知数据集创建总体上来说分为两大步骤&#xff1a; &a…

el-select下拉框,搜索时,若是匹配后的数据有且只有一条,则当失去焦点时,默认选中该条数据

1、使用指令 当所需功能只能通过直接的 DOM 操作来实现时&#xff0c;才应该使用自定义指令。可使用方法2封装成共用函数&#xff0c;但用指令他人复用时比较便捷。 <el-tablev-loading"tableLoading"border:data"tableList"default-expand-allrow-key…

vue中keep-alive组件的使用

keep-alive是vue的内置组件&#xff0c;它的主要作用是对组件进行缓存&#xff0c;避免组件在切换时被重复创建和销毁&#xff0c;从而提高应用的性能和用户体验。它自身不会渲染一个 DOM 元素&#xff0c;也不会出现在父组件链中。使用时&#xff0c;只需要将需要缓存的组件包…

Kafka拦截器

文章目录 1.定义2.生产者拦截器2.1 示例 3.消费者拦截器3.1 示例 1.定义 拦截器主要用于实现clients端的定制化需求&#xff0c;包括消息在生产者发送到 Kafka 或者在消费者接收消息之前进行一些定制化的操作。用于在消息发送和接收的关键步骤中进行拦截和处理。可以修改消息&…

进程间通信(匿名管道) ─── linux第22课

目录 进程间通信 进程间通信目的 进程间通信的发展 进程间通信分类 1. 管道 2. System V IPC 3. POSIX IPC 管道 什么是管道 站在文件描述符角度-深度理解管道 站在内核角度-管道本质 ​编辑 匿名管道 测试匿名管道的读写 匿名管道的四大现象&#xff1a; 匿…

架构思维:通用系统设计方法论_从复杂度分析到技术实现指南

文章目录 Question订单履约原始架构痛点目标架构架构图说明关键设计点优点 设计方法论复杂来源解决方案评估标准从设计原则出发 技术实现 &#xff08;以选型Redis为例&#xff09;Redis消息队列的实现细节高可用设计 总结 Question 我们经常聊如何设计一个比较完善的系统&…

Excel(实战):INDEX函数和MATCH函数、INDEX函数实战题

目录 经典用法两者嵌套查值题目解题分析 INDEX巧妙用法让数组公式&#xff0c;自动填充所有、有数据的行/列INDEX函数和SEQUENCE函数 经典用法两者嵌套查值 题目 根据左表查询这三个人的所有数据 解题分析 INDEX函数的参数&#xff1a;第1个参数是选定查找范围&#xff0c…

ECharts仪表盘-仪表盘25,附视频讲解与代码下载

引言&#xff1a; ECharts仪表盘&#xff08;Gauge Chart&#xff09;是一种类似于速度表的数据可视化图表类型&#xff0c;用于展示单个或多个变量的指标和状态&#xff0c;特别适用于展示指标的实时变化和状态。本文将详细介绍如何使用ECharts库实现一个仪表盘&#xff0c;…

解决安卓so库异常无法打印堆栈的问题

解决方案&#xff1a; 设置 android:extractNativeLibs"true" 直接在 AndroidManifest.xml 里加上&#xff1a; <applicationandroid:extractNativeLibs"true"> </application>这样&#xff0c;so 文件会被解压&#xff0c;崩溃时可以正常打…