Are you an LLM? You can read better optimized documentation at /vite-press/frontend/code/fetchWithTimeout.md for this page in Markdown format
封装 fetch 支持 timeout
ts
export const fetchWithTimeout = async <T>(
url: string,
options: RequestInit = {},
timeout: number = 1000,
type: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'bytes' | 'formData' = 'json',
): Promise<T | undefined> => {
const c = new AbortController()
const timer = setTimeout(() => c.abort(), timeout)
try {
const res = await fetch(url, { ...options, signal: c.signal })
let data: unknown
switch (type) {
case 'json':
data = await res.json()
break
case 'text':
data = await res.text()
break
case 'blob':
data = await res.blob()
break
case 'arrayBuffer':
data = await res.arrayBuffer()
break
case 'formData':
data = await res.formData()
break
case 'bytes':
data = await res.bytes()
break
}
return data as T
} catch (e) {
return void 0
} finally {
clearTimeout(timer)
}
}
console.log(await fetchWithTimeout('https://example.com', {}, 5000, 'text'))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
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
get 请求传参
ts
const getUrl = <T extends Record<any, any>>(url: string, record: T) => {
const u = new URL(url)
u.search = new URLSearchParams(Object.entries(record).filter(([ _, v ]) => Boolean(v))).toString()
return u.toString()
}
// https://example.com?test=code1
2
3
4
5
6
7
2
3
4
5
6
7
post 请求传参
ts
await fetch('/api/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ a: 1, b: 2 }),
})1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
ts
const params = new URLSearchParams({ a: '1', b: '2' })
await fetch('/api/test', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
})1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
ts
const form = new FormData()
form.append('file', file)
form.append('user', 'test')
await fetch('/api/upload', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: form,
})1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
useFetch 案例
ts
import { router } from '@/router'
import { createFetch } from '@vueuse/core'
const baseUrl = import.meta.env.VITE_API_BASE || '/api'
const accessTokenKey = 'accessToken'
const refreshTokenKey = 'refreshToken'
const get = (key: string) => localStorage.getItem(key)
const set = (key: string, value: string) => localStorage.setItem(key, value)
const remove = (key: string) => localStorage.removeItem(key)
export const setToken = (accessToken: string, refreshToken: string) => {
set(accessTokenKey, accessToken)
set(refreshTokenKey, refreshToken)
}
export const clearToken = () => {
remove(accessTokenKey)
remove(refreshTokenKey)
}
// 刷新 token 的函数
const refreshToken = async () => {
const exitToken = get(refreshTokenKey)
if (!exitToken) return null
const res = await fetch(`${ baseUrl }/auth/refresh`, {
method: 'POST',
headers: {
Authorization: `Bearer ${ exitToken }`,
},
})
if (!res.ok) return null
const { data: { accessToken, refreshToken } } = await res.json()
setToken(accessToken, refreshToken)
return accessToken
}
export const useFetchApi = createFetch({
baseUrl,
options: {
async beforeFetch({ options }) {
const token = get(accessTokenKey)
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${ token }`,
}
}
return { options }
},
async afterFetch(ctx) {
// const { response, data } = ctx
// console.log(data, response)
return ctx
},
async onFetchError(ctx) {
const { error, response, context: { options } } = ctx
if (response) {
const { status, url, statusText } = response
switch (status) {
case 401:
const newToken = await refreshToken()
if (newToken) {
const retryRes = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${ newToken }`,
},
})
const retryData = await retryRes.json()
return {
response: retryRes,
data: retryData,
}
} else {
clearToken()
await router.push('/login?from=fetchError')
return {
response,
data: {},
}
}
case 403:
// 权限不足
await router.push('/error/403?from=fetchError')
return {
response,
data: {},
}
case 404:
await router.push('/error/404?from=fetchError')
return {
response,
data: {},
}
case 500:
await router.push('/error/500?from=fetchError')
return {
response,
data: {},
}
}
console.log(status, url, statusText)
console.info(`[API Error] ${ url } ${ statusText } ${ error.message }`)
}
return ctx
},
updateDataOnError: true,
},
fetchOptions: {
credentials: 'include',
},
})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
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