通过使用 Fetch API,我们可以轻松地发起 HTTP 请求并处理响应,而无需引入额外的第三方库。
本文为一些常见的 HTTP 请求场景提供了 Fetch 示例。
Get JSON
fetch('http://example.com/api/v1/users/sb')
.then((res) => res.json())
.then((data) => {
console.log(data) // 输出 `res.json()` 的结果
})
.catch((err) => console.error(err))
自定义标头
fetch('http://example.com/api/v1/users/sb', {
headers: new Headers({
'User-agent': 'Mozilla/4.0 Custom User Agent',
}),
})
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error(err))
HTTP 错误处理
fetch('http://example.com/api/v1/users/sb')
.then((res) => (res.ok ? res.json() : Promise.reject(new Error('Fail to load'))))
.then((data) => console.log(data))
.catch((err) => console.error(err))
CORS
使用 credentials 选项来控制是否自动包含 cookie。
它有三个可选值:
omit:默认值,不包含凭据same-origin:同源请求时包含凭据include:所有请求都包含凭据
fetch('http://example.com/api/v1/users/sb', {
credentials: 'include',
})
.then((res) => res.json())
.then(console.log)
.catch(console.error)
Post JSON
function postRequest(url, data) {
return fetch(url, {
credentials: 'same-origin',
method: 'POST', // 'GET', 'PUT', 'DELETE' 等
body: JSON.stringify(data), // 匹配 'Content-Type'
headers: { 'Content-Type': 'application/json' },
})
.then((res) => res.json())
.catch((err) => console.error(err))
}
postRequest('http://example.com/api/v1/users', { user: 'sb' }).then(console.log)
Post <form>
function postForm(url, formSelector) {
const formData = new FormData(document.querySelector(formSelector))
return fetch(url, {
methods: 'POST',
body: formData,
})
.then((res) => res.json())
.catch((err) => console.error(err))
}
postForm('http://example.com/api/v1/users', '#user-form').then(console.log)
Form encoded data
使用 URLSearchParams 像查询字符串一样构建表单编码数据。
例如 new URLSearchParams({ a: '1', b: '2' }) 将返回 a=1&b=2。
function postFormData(url, data) {
return fetch(url, {
methods: 'POST',
body: new URLSearchParams(data),
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
}),
})
.then((res) => res.json())
.catch((err) => console.error(err))
}
postFormData('http://example.com/api/v1/users', { user: 'sb' }).then(console.log)
上传文件
function postFile(url, fileSelector) {
const formData = new FormData()
const fileField = document.querySelector(fileSelector)
formData.append('username', 'sb')
formData.append('avatar', fileField.files[0])
return fetch(url, {
method: 'POST',
body: formData,
})
}
postFile('http://example.com/api/v1/users', 'input[type="file"].avatar').then(console.log)
上传多个文件
HTML 中可以设置 mutiple 属性来允许用户选择多个文件。
<input type="file" name="files" class="files" multiple />
function postFile(url, fileSelector) {
const formData = new FormData()
const fileField = document.querySelector(fileSelector)
// 返回一个类数组对象,所以使用 Array.prototype.forEach 遍历
Array.prototype.forEach.call(fileField.files, (file) => formData.append('files', file))
return fetch(url, {
method: 'POST',
body: formData,
})
.then((res) => res.json())
.catch(console.error)
}
超时
function promiseTimeout(ms) {
return (promise) => {
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms)
})
return Promise.race([promise, timeout])
}
}
promiseTimeout(3000)(fetch('http://example.com/api/v1/users/sb'))
.then((res) => res.json())
.then(console.log)
.catch(console.error)
// 或者
function fetchTimeout(ms, ...args) {
function raceTimeout(promise) {
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms)
})
return Promise.race([promise, timeout])
}
return raceTimeout(fetch(...args))
}
更复杂的示例,具有跟踪标志 __timeout:
function promiseTimeout(ms) {
return (promise) => {
let isDone = false
promise.then(() => (isDone = true))
const timeout = new Promise((resolve, reject) => {
setTimeout(() => {
if (!isDone) {
promise.__timeout = true
reject(new Error('Timeout'))
}
}, ms)
})
return Promise.race([promise, timeout])
}
}
下载进度
function progressHelper(onProgress) {
return (response) => {
if (!response.body) return response
const contentLength = response.headers.get('content-length')
const total = contentLength ? parseInt(contentLength, 10) : -1
let loaded = 0
return new Response(
new ReadableStream({
start(controller) {
const reader = response.body.getReader()
return read()
function read() {
return reader
.read()
.then(({ done, value }) => {
if (done) return void controller.close()
loaded += value.byteLength
onProgress({ loaded, total })
controller.enqueue(value)
return read()
})
.catch((error) => {
console.error(error)
controller.error(error)
})
}
},
})
)
}
}
使用示例,更多可查看 fetch-progress-indicators。
fetch('https://fetch-progress.anthum.com/20kbps/images/sunrise-progressive.jpg')
.then(progressHelper(console.log))
.then((response) => response.blob())
.then((blob) => {
// 你可以在这里使用 blob
const img = document.createElement('img')
img.src = URL.createObjectURL(blob)
document.body.appendChild(img)
})
递归重试
function retryPromise(fn, retriesLeft = 5) {
return fn().catch((err) => (retriesLeft > 0 ? retryPromise(fn, retriesLeft - 1) : Promise.reject(err)))
}
const getJson = (url) => fetch(url).then((res) => res.json())
retry(() => getJson('http://example.com/api/v1/users/sb'))
.then(console.log)
.catch(console.error)
// 或者
function retryCurry(fn, retriesLeft = 5) {
const retryFn = (...args) => fn(...args)
.catch(err => retriesLeft > 0
? retryFn(fn, retriesLeft - 1)
: Promise.reject(err)
})
return retryFn
}
处理重定向
const checkForRedirect = (response) => {
// 307 temporary redirect
// 308 permanent redirect
if (response.status === 307 || response.status === 308) {
const location = response.headers.get('location')
if (!location) return Promise.reject(new Error('Invalid redirect'))
return fetch(location).then(checkForRedirect)
}
return response
}
fetch('http://example.com/api/v1/users/sb')
.then(checkForRedirect)
.then((res) => res.json())
.then(console.log)
.catch(console.error)