美文网首页
8-2、ts-axios其他功能实现

8-2、ts-axios其他功能实现

作者: Eileen_1d88 | 来源:发表于2019-12-17 10:42 被阅读0次
1、上传和下载进度监控
  • 需求
    有些时候,当我们上传文件或者是请求一个大体积数据的时候,希望知道实时的进度,甚至可以基于此做一个进度条提示。
    我们希望给axios的请求配置提供onDownloadProgress和onUploadProgress函数属性,用户可以通过这2个函数实现对上传进度和下载进度的监控。
axios.get('/more/get',{
  onDownloadProgress(progressEvent) {
    // 监听下载进度
  }
})

axios.post('/more/post',{
  onUploadProgress(progressEvent) {
    // 监听上传进度
  }
})

xhr对象提供了一个progress事件,我们可以监听此事件对数据的下载进度做监控;另外,xhr.upload对象也提供了progress事件,哦我们呢可以基于此对上传事件做监控。

  • 代码实现
    首先修改接口定义:
interface AxiosRequestConfig {
  // ...
  onDownloadProgress?: (e: ProgressEvent) => void
  onUploadProgress?: (e: ProgressEvent) => void
}

接着,在发送请求前,给xhr对象添加属性

export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  return new Promise((resolve, reject) => {
    const {
      // ...
      onDownloadProgress,
      onUploadProgress
    } = config
    if (onDownloadProgress) {
      request.onprogress = onDownloadProgress
    }
    if (onUploadProgress) {
      request.upload.onprogress = onUploadProgress
    }
  })
}

另外,如果请求的数据是FormData类型,我们应该主动删除请求headers中的Content-Type字段,让浏览器自动根据请求数据设置Content-Type。比如,当我们通过FormData上传文件的时候,浏览器会把请求headers中的Content-Type设置为multipart/form-data。

我们先添加一个判断FormData的方法:

function isFormData(val: any): boolean {
  return typeof val !== 'undefined' && val instanceof FormData
}

然后添加相关逻辑
xhr.ts

if (isFormData(data)) {
  delete headers['Content-Type']
}

我们发现,xhr函数内部随着需求越来越多,代码也越来越臃肿,我们可以把逻辑梳理一下,在代码内部做一层封装优化:

import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from "../types";
import { parseHeaders } from "../helpers/headers";
import { transformResponse } from "../helpers/data";
import { createError } from "../helpers/error";
import transform from './transform'
import { isURLSameOrigin } from "../helpers/url";
import cookie from "../helpers/cookie";
import { isFormData } from "../helpers/util";

export default function xhr(config: AxiosRequestConfig): AxiosPromise {
  return new Promise((resolve, reject) => {
    // method和data可能没有, 所以给出默认值
    const {
      url,
      method = 'get',
      data = null,
      headers,
      responseType,
      timeout,
      cancelToken,
      withCredentials,
      xsrfCookieName,
      xsrfHeaderName,
      onDownloadProgress,
      onUploadProgress
    } = config
    const request = new XMLHttpRequest()
    request.open(method.toUpperCase(), url!)
    configureRequest()
    addEvents()
    processHeaders()
    processCancel()
    request.send(data)

    function configureRequest(): void {
      if (responseType) {
        request.responseType = responseType
      }
      if (timeout) {
        request.timeout = timeout
      }
      if (withCredentials) {
        request.withCredentials = withCredentials
      }
    }

    function addEvents(): void {
      if (onDownloadProgress) {
        request.onprogress = onDownloadProgress
      }
      if (onUploadProgress) {
        request.upload.onprogress = onUploadProgress
      }
      request.onerror = function handleError() {
        reject(createError('Network Error', config, null, request))
      }
      request.onreadystatechange = function handleLoad() {
        // request.readyState不是4,说明请求还未返回
        if (request.readyState !== 4) {
          return
        }
        // 在请求未返回的时候,status为0,
        // 当XMLHttpRequest出错的时候,status也为0
        // 所以status为0的时候,不做处理
        if (request.status === 0) {
          return
        }
        const responseHeaders = parseHeaders(request.getAllResponseHeaders())
        const responseData = responseType && responseType !== 'text' ? request.response : request.responseText
        const response: AxiosResponse = {
          data: transform(responseData, responseHeaders, config.transformResponse),
          headers: responseHeaders,
          status: request.status,
          statusText: request.statusText,
          config,
          request
        }
        handleResponse(response)
      }
      request.ontimeout = function handleTimeout() {
        reject(createError(`Timeout of ${timeout}ms exceeded`, config, 'ECONNABORTED', request))
      }
    }

    function processHeaders(): void {
      if ((withCredentials || isURLSameOrigin(url!)) && xsrfCookieName) {
        const xsrfValue = cookie.read(xsrfCookieName)
        headers[xsrfHeaderName!] = xsrfValue
      }
      if (isFormData(data)) {
        delete headers['Content-Type']
      }
      Object.keys(headers).forEach((name) => {
        request.setRequestHeader(name, headers[name])
      })
    }

    function processCancel(): void {
      if (cancelToken) {
        cancelToken.promise.then(reason => {
          request.abort()
          reject(reason)
        })
      }
    }

    function handleResponse(response: AxiosResponse) {
      if (response.status >= 200 && response.status < 300) {
        resolve(response)
      } else {
        reject(createError(`Request failed width status code ${response.status}`, config, null, request, response))
      }
    }
  })
}

可以看到,整个流程分为7步:

  • 创建一个request实例
  • 执行request.open方法初始化
  • 执行configureRequest配置request对象
  • 执行addEvents给request添加事件处理函数
  • 执行processHeaders处理请求headers
  • 执行processCancel处理请求取消逻辑
  • 执行request.send方法发送请求
    这样分割可以使代码逻辑更清晰。

demo

// server
var multipart = require('connect-multiparty');
const path = require('path')
var multipartMiddleware = multipart({
  uploadDir: path.resolve(__dirname, '../upload-file')
});
router.post('/upload', multipartMiddleware,  (req, res) => {
  console.log(req.body, req.files)
  res.send('upload success')
})

// client

import axios, { Canceler } from '../../src/index'
const instance = axios.create()
const url = 'http://p1.meituan.net/dpmerchantimage/32874e71-23df-4bd9-84a3-4530eb582848.jpg%40740w_2048h_0e_1l%7Cwatermark%3D1%26%26r%3D1%26p%3D9%26x%3D2%26y%3D2%26relative%3D1%26o%3D20'
const downloadEl = document.getElementById('download')
downloadEl.addEventListener('click', e => {
  instance.get(url, {
    onDownloadProgress: (e) => {
      console.log('onDownloadProgress', e)
    }
  })
})
const uploadEl = document.getElementById('upload')
uploadEl.addEventListener('click', (e) => {
  const data = new FormData()
  const fileEl = document.getElementById('file') as HTMLInputElement
  if (fileEl.files) {
    console.log(fileEl.files)
    data.append('file', fileEl.files[0])
    instance.post('/api/upload', data, {
      onUploadProgress: (e) => {
        console.log('onUploadProgress', e)
      }
    }).then(res => {
      console.log(res)
    })
  }
})

//html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>demo2</title>
</head>
<body>
  <a>demo2</a>
  <button type="button" id="download">download</button>
  <form>
    <input type="file" id="file"/>
    <button type="button" id="upload">upload</button>
  </form>
  <script src="/dist/demo2.js"></script>
</body>
</html>

这样,我们可以在上传和下载的时候,监听到进度变化。

相关文章

  • 8-2、ts-axios其他功能实现

    1、上传和下载进度监控 需求有些时候,当我们上传文件或者是请求一个大体积数据的时候,希望知道实时的进度,甚至可以基...

  • 六、实现拦截器功能

    本篇我们来实现ts-axios的拦截功能,拦截功能就是可以在请求发送之前和受到服务端接口返回的数据之后做一些工作。...

  • 7、ts-axios 取消功能

    需求 有些场景下,我们希望能主动取消请求,比如常见的搜索框案例,在用户输入过程中,搜索框的内容也在不断变化,正常情...

  • 文学功能之间的关系2

    其中,审美功能是文学的基本功能,它是文学实现其他各种功能的中介,读者通过审美活动来把握文学形象和思想内涵,其他功能...

  • 五、重构项目结构

    在前面的几章内容中,我们已经实现了ts-axios的发送请求、处理请求、Promise化等操作,同时也处理错误的处...

  • Vue+Django REST framework 打造生鲜电商

    8-1 viewset实现详情页接口 8-2 热卖商品接口实现 models中is_hot字段,使用过滤查询goo...

  • 2018-12-01

    万年历查询(JavaScript实现) 实现的界面有点单调,查询功能已经实现,之后还可以添加一下其他功能。。 效果...

  • 3-1、处理异常情况

    需求 之前的内容,我们完成了ts-axios的基础功能,但目前为止,我们都是处理的正常接收请求的情况,这对于一个程...

  • 四、错误处理-需求分析-网络错误+非200状态码

    在前一章节,我们实现了ts-axios的Promise封装和构建,完成了请求发送成功和成功获取数据后的处理操作。本...

  • selenium和pywinauto实现贴吧自动发帖

    说明:本文仅供学习参考;请勿用作其他用途 1.功能 已经实现:实现贴吧自动发帖功能(包括标题,内容,上传图片),标...

网友评论

      本文标题:8-2、ts-axios其他功能实现

      本文链接:https://www.haomeiwen.com/subject/dnnfnctx.html