目录结构 |-index.html |-server.js |-client.js
const http = require('http')
const path = require('path')
const fs = require('fs')
const router = (req, res) => {
const { url } = req
if (url === '/') {
res.end(fs.readFileSync(path.resolve(__dirname, './index.html')))
} else if (url.includes('api')) {
res.end('123')
} else if (url.includes('.js')) {
res.end(fs.readFileSync(path.resolve(process.cwd(), '.' + url)))
} else {
res.end(JSON.stringify({ message: '404 NotFound' }))
}
}
const server = http.createServer((req, res) => {
router(req, res)
})
server.listen(3000, () => {
console.log('server is listening on port 3000');
})
fetch 不能实现请求进度
基本使用
const header = new Headers({
'Content-Type': 'application/json',
'X-Token': 'konsoue'
})
const request = new Request('/api/123', {
method: 'GET',
headers: header
})
fetch(request)
.then((response) => {
const {
url,
type,
status,
statusText,
ok,
headers: responseHeaders,
redirected
} = response
return response.json()
})
.then((response) => {
console.log(response)
})
解决超时
当网络出现异常,请求可能已经超时,为了使我们的程序更健壮,提供一个较好的用户体验,我们需要提供一个超时机制。然而 fetch 并不支持。庆幸的是,我们有 Promise ,这使得我们可以通过自定义封装来达到支持超时机制。下面我们尝试封装下。
超时机制:超时自动中断。
/**
* 自定义请求函数
* @param {String} url
* @param {Object} options
* @returns
*/
const _fetch = (url, options) => {
const requestHeaders = new Headers({
'Content-Type': 'application/json',
...options.headers,
})
const request = new Request(url, requestHeaders)
return new Promise((resolve, reject) => {
let abortId = null, timeout = false
if (options.timeout) {
abortId = setTimeout(() => {
timeout = true
reject(new Error('timeout'))
}, options.timeout)
}
fetch(request)
.then(response => {
if (timeout) throw new Error('timeout')
resolve(response)
})
.catch(error => {
clearTimeout(timeout)
reject(error)
})
})
}
:::info
上面的代码中,我们需要注意下。就是我们手动根据超时时间来 reject
并不会阻止后续的请求,由于我们并没有关闭掉此次连接,属于是伪取消。fetch 中如果后续接受到服务器的响应,依然会继续处理后续的处理。所以这里我们在 fetch 的第一个 then
中进行了超时判断。
:::
// 测试用例
_fetch('/good', {
method: 'GET',
timeout: 5000
})
.then(response => {
console.log(response)
return response.json()
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
中断请求
我们回过头看下 fetch 的接口,发现有一个属性 signal
, 类型为 AbortSignal,表示一个信号对象,它允许你通过 AbortController 对象与DOM请求进行通信并在需要时将其中止。你可以通过调用 AbortController.abort
方法完成中断的操作。
当我们需要取消时,fetch 会 reject 一个错误( AbortError DOMException ),中断你的后续处理逻辑。具体可以看规范中的解释。
:::warning 需要注意的是 AbortController 的兼容性不好,生产环境使用需要谨慎考虑。 :::
const controller = new AbortController();
const signal = controller.signal;
fetch('/data?name=fe', {
method: 'GET',
signal,
})
.then(response => {
console.log(response)
})
.catch(error => {
console.error(error)
})
// 中断请求
controller.abort();
拦截器
这里的拦截器指对响应做拦截。假设我们需要对接口返回的状态码进行解析,例如 403 或者 401 需要跳转到登录页面,200 正常放行,其他报错。由于 fetch 返回一个 Promise ,这就使得我们可以在后续的 then
中做些简单的拦截。
/**
* 自定义请求函数
* @param {String} url
* @param {Object} options
* @returns
*/
const _fetch = (url, options) => {
const requestHeaders = new Headers({
'Content-Type': 'application/json',
...options.headers,
})
const request = new Request(url, requestHeaders)
return new Promise((resolve, reject) => {
let abortId = null, timeout = false
if (options.timeout) {
abortId = setTimeout(() => {
timeout = true
reject(new Error('timeout'))
}, options.timeout)
}
fetch(request)
.then(_fetch.ResponseInterceptor || (a => a)) // 拦截响应就是加这一行
.then(response => {
if (timeout) throw new Error('timeout')
resolve(response)
})
.catch(error => {
clearTimeout(timeout)
reject(error)
})
})
}
// 对结果进行拦截
const checkStatus = (response) => {
console.log(response)
const { status } = response
if (status >= 200 && status < 300) return response
if (status === 403 || status === 401) {
if (window) window.location = '/login.html'
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
_fetch.ResponseInterceptor = checkStatus
_fetch('/api', { timeout: 5000 })