目录结构 |-index.html |-server.js |-client.js

  1. const http = require('http')
  2. const path = require('path')
  3. const fs = require('fs')
  4. const router = (req, res) => {
  5. const { url } = req
  6. if (url === '/') {
  7. res.end(fs.readFileSync(path.resolve(__dirname, './index.html')))
  8. } else if (url.includes('api')) {
  9. res.end('123')
  10. } else if (url.includes('.js')) {
  11. res.end(fs.readFileSync(path.resolve(process.cwd(), '.' + url)))
  12. } else {
  13. res.end(JSON.stringify({ message: '404 NotFound' }))
  14. }
  15. }
  16. const server = http.createServer((req, res) => {
  17. router(req, res)
  18. })
  19. server.listen(3000, () => {
  20. console.log('server is listening on port 3000');
  21. })

fetch 不能实现请求进度

基本使用

  1. const header = new Headers({
  2. 'Content-Type': 'application/json',
  3. 'X-Token': 'konsoue'
  4. })
  5. const request = new Request('/api/123', {
  6. method: 'GET',
  7. headers: header
  8. })
  9. fetch(request)
  10. .then((response) => {
  11. const {
  12. url,
  13. type,
  14. status,
  15. statusText,
  16. ok,
  17. headers: responseHeaders,
  18. redirected
  19. } = response
  20. return response.json()
  21. })
  22. .then((response) => {
  23. console.log(response)
  24. })

解决超时

当网络出现异常,请求可能已经超时,为了使我们的程序更健壮,提供一个较好的用户体验,我们需要提供一个超时机制。然而 fetch 并不支持。庆幸的是,我们有 Promise ,这使得我们可以通过自定义封装来达到支持超时机制。下面我们尝试封装下。

超时机制:超时自动中断。

  1. /**
  2. * 自定义请求函数
  3. * @param {String} url
  4. * @param {Object} options
  5. * @returns
  6. */
  7. const _fetch = (url, options) => {
  8. const requestHeaders = new Headers({
  9. 'Content-Type': 'application/json',
  10. ...options.headers,
  11. })
  12. const request = new Request(url, requestHeaders)
  13. return new Promise((resolve, reject) => {
  14. let abortId = null, timeout = false
  15. if (options.timeout) {
  16. abortId = setTimeout(() => {
  17. timeout = true
  18. reject(new Error('timeout'))
  19. }, options.timeout)
  20. }
  21. fetch(request)
  22. .then(response => {
  23. if (timeout) throw new Error('timeout')
  24. resolve(response)
  25. })
  26. .catch(error => {
  27. clearTimeout(timeout)
  28. reject(error)
  29. })
  30. })
  31. }

:::info 上面的代码中,我们需要注意下。就是我们手动根据超时时间来 reject 并不会阻止后续的请求,由于我们并没有关闭掉此次连接,属于是伪取消。fetch 中如果后续接受到服务器的响应,依然会继续处理后续的处理。所以这里我们在 fetch 的第一个 then 中进行了超时判断。 :::

  1. // 测试用例
  2. _fetch('/good', {
  3. method: 'GET',
  4. timeout: 5000
  5. })
  6. .then(response => {
  7. console.log(response)
  8. return response.json()
  9. })
  10. .then(result => {
  11. console.log(result)
  12. })
  13. .catch(error => {
  14. console.log(error)
  15. })

中断请求

我们回过头看下 fetch 的接口,发现有一个属性 signal, 类型为 AbortSignal,表示一个信号对象,它允许你通过 AbortController 对象与DOM请求进行通信并在需要时将其中止。你可以通过调用 AbortController.abort 方法完成中断的操作。

当我们需要取消时,fetch 会 reject 一个错误( AbortError DOMException ),中断你的后续处理逻辑。具体可以看规范中的解释

:::warning 需要注意的是 AbortController 的兼容性不好,生产环境使用需要谨慎考虑。 :::

  1. const controller = new AbortController();
  2. const signal = controller.signal;
  3. fetch('/data?name=fe', {
  4. method: 'GET',
  5. signal,
  6. })
  7. .then(response => {
  8. console.log(response)
  9. })
  10. .catch(error => {
  11. console.error(error)
  12. })
  13. // 中断请求
  14. controller.abort();

拦截器

这里的拦截器指对响应做拦截。假设我们需要对接口返回的状态码进行解析,例如 403 或者 401 需要跳转到登录页面,200 正常放行,其他报错。由于 fetch 返回一个 Promise ,这就使得我们可以在后续的 then 中做些简单的拦截。

  1. /**
  2. * 自定义请求函数
  3. * @param {String} url
  4. * @param {Object} options
  5. * @returns
  6. */
  7. const _fetch = (url, options) => {
  8. const requestHeaders = new Headers({
  9. 'Content-Type': 'application/json',
  10. ...options.headers,
  11. })
  12. const request = new Request(url, requestHeaders)
  13. return new Promise((resolve, reject) => {
  14. let abortId = null, timeout = false
  15. if (options.timeout) {
  16. abortId = setTimeout(() => {
  17. timeout = true
  18. reject(new Error('timeout'))
  19. }, options.timeout)
  20. }
  21. fetch(request)
  22. .then(_fetch.ResponseInterceptor || (a => a)) // 拦截响应就是加这一行
  23. .then(response => {
  24. if (timeout) throw new Error('timeout')
  25. resolve(response)
  26. })
  27. .catch(error => {
  28. clearTimeout(timeout)
  29. reject(error)
  30. })
  31. })
  32. }
  1. // 对结果进行拦截
  2. const checkStatus = (response) => {
  3. console.log(response)
  4. const { status } = response
  5. if (status >= 200 && status < 300) return response
  6. if (status === 403 || status === 401) {
  7. if (window) window.location = '/login.html'
  8. }
  9. const error = new Error(response.statusText);
  10. error.response = response;
  11. throw error;
  12. }
  13. _fetch.ResponseInterceptor = checkStatus
  14. _fetch('/api', { timeout: 5000 })