fix: 修复微信开发者工具 60 秒超时错误处理问题
- abort() 只清理资源,不再触发 onError(避免正常流程误报错误) - 真正的错误(超时、网络错误)通过 Promise.race 的 catch 正确传递给用户 - 修复微信开发者工具强制终止 fetch 时 onError 不被调用的问题 - 使用 errorHandled 标志防止 onError 重复调用
This commit is contained in:
@@ -212,8 +212,9 @@ function h5NovelStream(url, body, onEvent, onError) {
|
|||||||
let reader = null
|
let reader = null
|
||||||
let timeoutTimer = null
|
let timeoutTimer = null
|
||||||
let isCompleted = false
|
let isCompleted = false
|
||||||
|
let errorHandled = false // 防止 onError 重复调用
|
||||||
|
|
||||||
// 统一的清理函数
|
// 统一的清理函数(不处理错误,只清理资源)
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
isCompleted = true
|
isCompleted = true
|
||||||
if (timeoutTimer) clearTimeout(timeoutTimer)
|
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)
|
// 创建超时 Promise(不依赖 AbortController)
|
||||||
const timeoutPromise = new Promise((_, reject) => {
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
timeoutTimer = setTimeout(() => {
|
timeoutTimer = setTimeout(() => {
|
||||||
if (!isCompleted) {
|
if (!isCompleted) {
|
||||||
cleanup()
|
|
||||||
reject(new Error('请求超时(300 秒)'))
|
reject(new Error('请求超时(300 秒)'))
|
||||||
}
|
}
|
||||||
}, SSE_TIMEOUT_MS)
|
}, SSE_TIMEOUT_MS)
|
||||||
@@ -244,9 +253,7 @@ function h5NovelStream(url, body, onEvent, onError) {
|
|||||||
}).then(response => {
|
}).then(response => {
|
||||||
// 检查响应状态
|
// 检查响应状态
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
cleanup()
|
throw new Error(`请求失败:HTTP ${response.status}`)
|
||||||
onError?.(`请求失败:HTTP ${response.status}`)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 ReadableStream 读取器
|
// 获取 ReadableStream 读取器
|
||||||
@@ -257,8 +264,14 @@ function h5NovelStream(url, body, onEvent, onError) {
|
|||||||
// 循环读取流数据
|
// 循环读取流数据
|
||||||
function pump() {
|
function pump() {
|
||||||
return reader.read().then(({ done, value }) => {
|
return reader.read().then(({ done, value }) => {
|
||||||
if (done || isCompleted) {
|
if (done) {
|
||||||
cleanup()
|
// 流正常结束,标记为已完成(后续 abort 不触发错误)
|
||||||
|
isCompleted = true
|
||||||
|
if (timeoutTimer) clearTimeout(timeoutTimer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,9 +288,6 @@ function h5NovelStream(url, body, onEvent, onError) {
|
|||||||
|
|
||||||
// 继续读取
|
// 继续读取
|
||||||
return pump()
|
return pump()
|
||||||
}).catch(err => {
|
|
||||||
cleanup()
|
|
||||||
throw err
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,17 +295,23 @@ function h5NovelStream(url, body, onEvent, onError) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 使用 Promise.race 竞争超时和 fetch
|
// 使用 Promise.race 竞争超时和 fetch
|
||||||
Promise.race([fetchPromise, timeoutPromise]).catch(err => {
|
Promise.race([fetchPromise, timeoutPromise]).then(() => {
|
||||||
if (!isCompleted) {
|
// 成功完成,清理资源
|
||||||
cleanup()
|
if (timeoutTimer) clearTimeout(timeoutTimer)
|
||||||
onError?.(err.message || '请求超时')
|
if (reader) reader.cancel().catch(() => {})
|
||||||
}
|
}).catch(err => {
|
||||||
|
// 任何错误都传递给用户(包括微信开发者工具的 60 秒强制终止)
|
||||||
|
safeOnError(err.message || '请求超时')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 返回 abort 接口
|
// 返回 abort 接口(只清理资源,不触发错误)
|
||||||
return {
|
return {
|
||||||
abort: () => {
|
abort: () => {
|
||||||
cleanup()
|
// abort 只负责取消请求和清理资源,不触发 onError
|
||||||
|
// 真正的错误(超时、网络错误)会通过 Promise.race 的 catch 触发
|
||||||
|
isCompleted = true
|
||||||
|
if (timeoutTimer) clearTimeout(timeoutTimer)
|
||||||
|
if (reader) reader.cancel().catch(() => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user