目录结构 |-index.html |-server.js |-client.js
const http = require('http')const path = require('path')const fs = require('fs')const router = (req, res) => {const { url } = reqif (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} = responsereturn 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 = falseif (options.timeout) {abortId = setTimeout(() => {timeout = truereject(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 = falseif (options.timeout) {abortId = setTimeout(() => {timeout = truereject(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 } = responseif (status >= 200 && status < 300) return responseif (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 })
