(
url: string,
options: RequestInit = {},
timeoutMs: number = API_TIMEOUT.DEFAULT
)
| 323 | * @returns 响应数据或抛出错误 |
| 324 | */ |
| 325 | export async function fetchWithTimeout<T>( |
| 326 | url: string, |
| 327 | options: RequestInit = {}, |
| 328 | timeoutMs: number = API_TIMEOUT.DEFAULT |
| 329 | ): Promise<T> { |
| 330 | const controller = new AbortController() |
| 331 | const timeoutId = setTimeout(() => controller.abort(), timeoutMs) |
| 332 | |
| 333 | try { |
| 334 | const response = await fetch(url, { |
| 335 | ...options, |
| 336 | signal: controller.signal, |
| 337 | }) |
| 338 | |
| 339 | if (!response.ok) { |
| 340 | throw AppError.api( |
| 341 | `HTTP ${response.status}: ${response.statusText}`, |
| 342 | `HTTP_${response.status}`, |
| 343 | { url } |
| 344 | ) |
| 345 | } |
| 346 | |
| 347 | return (await response.json()) as T |
| 348 | } catch (error) { |
| 349 | if (error instanceof DOMException && error.name === 'AbortError') { |
| 350 | throw AppError.timeout(url, timeoutMs) |
| 351 | } |
| 352 | if (error instanceof AppError) { |
| 353 | throw error |
| 354 | } |
| 355 | if (error instanceof Error) { |
| 356 | throw AppError.network(error.message, error) |
| 357 | } |
| 358 | throw AppError.api(String(error), 'FETCH_FAILED') |
| 359 | } finally { |
| 360 | clearTimeout(timeoutId) |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | /** |
| 365 | * 可取消的请求包装器 |
no test coverage detected