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(() => {}) } } }