From 1cf75a2b407f63b1b58d8ebc4e2b0913e41ccaec Mon Sep 17 00:00:00 2001 From: Peanut Date: Thu, 23 Jul 2026 22:21:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=80=85=E5=B7=A5=E5=85=B7=2060=20=E7=A7=92?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - abort() 只清理资源,不再触发 onError(避免正常流程误报错误) - 真正的错误(超时、网络错误)通过 Promise.race 的 catch 正确传递给用户 - 修复微信开发者工具强制终止 fetch 时 onError 不被调用的问题 - 使用 errorHandled 标志防止 onError 重复调用 --- mini-program/src/services/shortNovel.js | 50 ++++++++++++++++--------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/mini-program/src/services/shortNovel.js b/mini-program/src/services/shortNovel.js index d84b4e9..597fa30 100644 --- a/mini-program/src/services/shortNovel.js +++ b/mini-program/src/services/shortNovel.js @@ -212,8 +212,9 @@ function h5NovelStream(url, body, onEvent, onError) { let reader = null let timeoutTimer = null let isCompleted = false + let errorHandled = false // 防止 onError 重复调用 - // 统一的清理函数 + // 统一的清理函数(不处理错误,只清理资源) const cleanup = () => { isCompleted = true if (timeoutTimer) clearTimeout(timeoutTimer) @@ -222,11 +223,19 @@ function h5NovelStream(url, body, onEvent, onError) { } } + // 安全的错误处理(确保 onError 只调用一次) + const safeOnError = (msg) => { + if (!errorHandled) { + errorHandled = true + cleanup() + onError?.(msg) + } + } + // 创建超时 Promise(不依赖 AbortController) const timeoutPromise = new Promise((_, reject) => { timeoutTimer = setTimeout(() => { if (!isCompleted) { - cleanup() reject(new Error('请求超时(300 秒)')) } }, SSE_TIMEOUT_MS) @@ -244,9 +253,7 @@ function h5NovelStream(url, body, onEvent, onError) { }).then(response => { // 检查响应状态 if (!response.ok) { - cleanup() - onError?.(`请求失败:HTTP ${response.status}`) - return + throw new Error(`请求失败:HTTP ${response.status}`) } // 获取 ReadableStream 读取器 @@ -257,8 +264,14 @@ function h5NovelStream(url, body, onEvent, onError) { // 循环读取流数据 function pump() { return reader.read().then(({ done, value }) => { - if (done || isCompleted) { - cleanup() + if (done) { + // 流正常结束,标记为已完成(后续 abort 不触发错误) + isCompleted = true + if (timeoutTimer) clearTimeout(timeoutTimer) + return + } + + if (isCompleted) { return } @@ -275,9 +288,6 @@ function h5NovelStream(url, body, onEvent, onError) { // 继续读取 return pump() - }).catch(err => { - cleanup() - throw err }) } @@ -285,17 +295,23 @@ function h5NovelStream(url, body, onEvent, onError) { }) // 使用 Promise.race 竞争超时和 fetch - Promise.race([fetchPromise, timeoutPromise]).catch(err => { - if (!isCompleted) { - cleanup() - onError?.(err.message || '请求超时') - } + Promise.race([fetchPromise, timeoutPromise]).then(() => { + // 成功完成,清理资源 + if (timeoutTimer) clearTimeout(timeoutTimer) + if (reader) reader.cancel().catch(() => {}) + }).catch(err => { + // 任何错误都传递给用户(包括微信开发者工具的 60 秒强制终止) + safeOnError(err.message || '请求超时') }) - // 返回 abort 接口 + // 返回 abort 接口(只清理资源,不触发错误) return { abort: () => { - cleanup() + // abort 只负责取消请求和清理资源,不触发 onError + // 真正的错误(超时、网络错误)会通过 Promise.race 的 catch 触发 + isCompleted = true + if (timeoutTimer) clearTimeout(timeoutTimer) + if (reader) reader.cancel().catch(() => {}) } } }