-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
164 lines (140 loc) · 5.31 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import InterceptorManager from './utils/interceptorManager'
type Config = {
timeout?: number // 请求超时时间
retries?: number // 请求超时重试次数
retryInterval?: number // 添加重试间隔时间参数
retryOnFail?: boolean // 是否开启错误重试
apiUrl?: string // 基础URL
withHeader?: boolean // 是否携带自定义header
}
export interface RequestOptions extends RequestInit {
_timeout?: number
_retries?: number
_retryInterval?: number
_retryOnFail?: boolean
_apiUrl?: string
_withHeader?: boolean
_isReturnNativeResponse?: boolean
}
interface RequestContext {
url: string
options: RequestOptions
}
interface ResponseContext {
response: Response
options: RequestOptions
}
class FetchWrapper {
private timeout: number
private retries: number
private apiUrl: string
private retryInterval: number
private retryOnFail: boolean
public interceptors = {
request: new InterceptorManager<RequestContext>(),
response: new InterceptorManager<ResponseContext>()
}
constructor(config: Config) {
this.timeout = config.timeout || 5000 // 默认超时时间为5000毫秒
this.retries = config.retries || 0 // 默认重试次数为0
this.apiUrl = config.apiUrl || '' // 如果没有配置baseURL,默认为空
this.retryInterval = config.retryInterval || 1000 // 如果未设置,默认为1000毫秒
this.retryOnFail = config.retryOnFail !== undefined ? config.retryOnFail : false // 初始化错误重试开关,默认值为 false
}
private async fetchWithTimeout(resource: string, options: RequestOptions) {
const timeout = options._timeout !== undefined ? options._timeout : this.timeout
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), timeout)
options.signal = controller.signal
const finalResource =
options._apiUrl === undefined ? this.apiUrl + resource : options._apiUrl + resource
try {
const response = await fetch(finalResource, options)
clearTimeout(id)
return response
} catch (error) {
throw new Error('Request timed out')
}
}
private async retryFetch(resource: string, options: RequestOptions): Promise<Response> {
const retries = options._retries !== undefined ? options._retries : this.retries
let interval =
options._retryInterval !== undefined ? options._retryInterval : this.retryInterval
for (let i = 0; i <= retries; i++) {
try {
const response = await this.fetchWithTimeout(resource, options)
if (!response.ok) {
const status = response.status
if (status >= 500 || status === 429) {
if (status === 429 && response.headers.has('Retry-After')) {
interval = parseInt(response.headers.get('Retry-After')!, 10) * 1000
}
throw new Error(`Network response was not ok: ${status}`)
}
}
return response
} catch (error) {
console.error(`Attempt ${i + 1} failed: ${(error as Error).message}`)
if (i < retries) {
await new Promise(resolve => setTimeout(resolve, interval))
} else {
throw error
}
}
}
throw new Error('Maximum retries exceeded')
}
async request(resource: string, options: RequestOptions = {}): Promise<Response> {
const retryOnFail = options._retryOnFail !== undefined ? options._retryOnFail : this.retryOnFail
const requestContext: RequestContext = { url: resource, options }
try {
const resolvedRequestContext = await this.interceptors.request.runHandlers(requestContext)
let response: Response
if (!retryOnFail) {
response = await this.fetchWithTimeout(
resolvedRequestContext.url,
resolvedRequestContext.options
)
} else {
response = await this.retryFetch(resolvedRequestContext.url, resolvedRequestContext.options)
}
const responseContext: ResponseContext = {
response,
options: resolvedRequestContext.options
}
const resolvedResponseContext = await this.interceptors.response.runHandlers(responseContext)
return resolvedResponseContext.response
} catch (error) {
const errorContext = { error, request: requestContext }
console.error('Request failed:', errorContext)
throw error
}
}
// 封装 GET 方法
async get<T = Response>(resource: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request(resource, { ...options, method: 'GET' }) as T
}
// 封装 POST 方法
async post<T = Response>(resource: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request(resource, {
...options,
headers: { 'Content-Type': 'application/json', ...options.headers },
method: 'POST',
body: options.body
}) as T
}
// 封装 PUT 方法
async put<T = Response>(resource: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request(resource, {
...options,
headers: { 'Content-Type': 'application/json', ...options.headers },
method: 'PUT',
body: options.body
}) as T
}
// 封装 DELETE 方法
async delete<T = Response>(resource: string, options: Omit<RequestOptions, 'method'> = {}) {
return this.request(resource, { ...options, method: 'DELETE' }) as T
}
}
export default FetchWrapper