zoukankan      html  css  js  c++  java
  • 前端接口请求之Fetch方法封装、业务实践

      说Fetch之前啊,我们不得不说一说Ajax了,以前使用最多的自然是jQuery封装的Ajax方法了,强大而且好用。

      有人说了,jQuery的Ajax都已经封装得那么好了,你还整Fetch干什么,这不是多此一举嘛。实际上,在大家写业务需求的时候能够感觉到,越是复杂的业务逻辑,Ajax用起来就会显得有点费劲,因为它的异步回调机制,很容易让我们陷入嵌套循环中,业务代码就会变得非常臃肿而难以理解。而Fetch请求则可以写成同步的模式,一步一步执行,阅读起来就非常舒服。

      有人又说了,Ajax也能写成同步的啊,用Promise封装一个就好了。你说得对,可那不是有点多余吗,有现成的你不用,还得搭上一个jQuery,不划算。总之,我们使用Fetch来进行请求,轻量而且方便。

      题外话:你把接口写完了,是不是得了解一下接口请求是不是成功啦,请求成功率有多高呢;接口耗时怎么样啊,超时的接口又有多少;那么如何监控接口请求的性能问题,可以看下我写的前端监控系统:www.webfunny.cn 或者github搜索:webfunny_monitor 欢迎了解。

      题外话说完,下边开始说干货了。

      Fetch本身其实跟Ajax一样都是对XMLHttpRequest的封装,而且封装的很不错了,为什么还要封装呢,我们且看下fetch请求的写法。

    // fetch原本方法的写法
    fetch(url, {
        method: 'GET';
    }).then(res => {
        res.json();
    }).then(data => {
        console.log(data);
    }).catch(error => {
        console.log(error.msg);
    })

      请看,上边的代码算是最简单的fetch请求方法了,写出来是这个样子的,虽然简单,但是调用链也忒长了点儿啊,我们在看下相对较复杂一点的fetch请求写法。

    // fetch原本方法的写法
        fetch(url, {
          method: "POST",
          headers: {
            "Accept": "*/*",
            "Content-Type": "application/json;charset=utf-8",
            "access-token": "token"
          }
        }).then(res => {
          res.json()
        }).then(data => {
          console.log(data)
        }).catch(error => {
          console.log(error.msg)
        })
      }

      上边带一坨headers, 下边带一坨catch,十分恶心。像这个简单的调用方法,还没有处理超时、报错、404等,都够咱们喝一壶了,所以业务实践,Fetch方法必须要封装。

      

      一、区分请求类型  

      一般请求的方式有多重类型如:PUT,POST,GET,DELETE;今天我们就以最常见的post、get为例进行封装。不管是用什么方法请求,请求所需要的参数和逻辑处理都是大致相同的,所以我们要做的就是把这些共用的部分提取出来。

      我们先来解释一下它的参数,url:请求路径;param:请求参数,get、post分别用不同的方式传入;httpCustomerOpertion(自定义参数),isHandleResult:是否需要处理错误结果,比如toast错误信息,上报错误信息等等;isShowLoading:是否显示loading,每个接口请求都需要时间,是否显示loading效果;customerHead:是否使用自定义的请求头;timeout: 自定义接口超时的时间

      以上参数虽然繁琐,但都是很有必要的,代码如下:

     /**
       * get 请求
       * @param url
       * @param params
       * @param isHandleError
       * @param httpCustomerOpertion 使用者传递过来的参数, 用于以后的扩展用户自定义的行为
       * {
       *    isHandleResult: boolen    //是否需要处理错误结果   true 需要/false 不需要
       *    isShowLoading: boolen     //是否需要显示loading动画
       *    customHead: object        // 自定义的请求头
       *    timeout: int              //自定义接口超时的时间
       * }
       * @returns {Promise}
       */
      get(url, params = {}, httpCustomerOpertion = { isHandleResult: true, isShowLoading: true  }) {
        if (!httpCustomerOpertion.hasOwnProperty("isHandleResult")) {
          httpCustomerOpertion.isHandleResult = true
        }
        if (!httpCustomerOpertion.hasOwnProperty("isShowLoading")) {
          httpCustomerOpertion.isShowLoading = true
        }
        const method = "GET"
        const fetchUrl = url + CommonTool.qs(params) // 将参数转化到url上
        const fetchParams = Object.assign({}, { method }, this.getHeaders())
        return HttpUtil.handleFetchData(fetchUrl, fetchParams, httpCustomerOpertion)
      }
    
     /**
       * post 请求
       * @param url
       * @param params
       * @param isHandleError
       * @param httpCustomerOpertion 使用者传递过来的参数, 用于以后的扩展用户自定义的行为
       * @returns {Promise}
       */
      post(url, params = {}, httpCustomerOpertion = { isHandleResult: true, isShowLoading: true  }) {
        if (!httpCustomerOpertion.hasOwnProperty("isHandleResult")) {
          httpCustomerOpertion.isHandleResult = true
        }
        if (!httpCustomerOpertion.hasOwnProperty("isShowLoading")) {
          httpCustomerOpertion.isShowLoading = true
        }
        const method = "POST"
        const body = JSON.stringify(params) // 将参数转化成JSON字符串
        const fetchParams = Object.assign({}, { method, body }, this.getHeaders())
        return HttpUtil.handleFetchData(url, fetchParams, httpCustomerOpertion)
      }

      

      二、fetch请求业务逻辑封装

      请求方式区分开了,接下来我们对fetch的核心内容进行封装。

      1. 判断自定义请求信息,loading,header。

      2. 对fetch请求再进行一次封装。

      3. 放弃迟到的响应,轮询时,有可能发生这种情况,后边发出的接口请求,提前获得了返回结果,就需要放弃前一次的请求结果。

      4. 统一处理返回结果,后台业务返回结果,无论结果如何,说明接口正常。业务逻辑再区分正常和异常,比如token超时,业务异常等等。

      5. 接口状态判断,第4点说了,返回结果,无论结果如何,接口都是正常的;如果接口状态是非200类型的,说明接口本身出错了,如404,500等,需要单独捕获和处理。

      6. 接口超时处理,由于fetch本身并不支持超时判断,所以我们需要利用promise.race方法来判断超时问题。

      核心代码如下:

      /**
      * 发送fetch请求 * @param fetchUrl * @param fetchParams * @returns {Promise}
    */ static handleFetchData(fetchUrl, fetchParams, httpCustomerOpertion) {
       // 1. 处理的第一步 const { isShowLoading } = httpCustomerOpertion if (isShowLoading) { HttpUtil.showLoading() } httpCustomerOpertion.isFetched = false httpCustomerOpertion.isAbort = false // 处理自定义的请求头 if (httpCustomerOpertion.hasOwnProperty("customHead")) { const { customHead } = httpCustomerOpertion fetchParams.headers = Object.assign({}, fetchParams.headers, customHead) }
       // 2. 对fetch请求再进行一次Promise的封装 const fetchPromise = new Promise((resolve, reject) => { fetch(fetchUrl, fetchParams).then( response => {
          // 3. 放弃迟到的响应
    if (httpCustomerOpertion.isAbort) { // 3. 请求超时后,放弃迟到的响应 return } if (isShowLoading) { HttpUtil.hideLoading() } httpCustomerOpertion.isFetched = true response.json().then(jsonBody => { if (response.ok) {
             // 4. 统一处理返回结果
    if (jsonBody.status === 5) { // token失效,重新登录 CommonTool.turnToLogin() } else if (jsonBody.status) { // 业务逻辑报错, 不属于接口报错的范畴 reject(HttpUtil.handleFailedResult(jsonBody, httpCustomerOpertion)) } else { resolve(HttpUtil.handleResult(jsonBody, httpCustomerOpertion)) } } else {
             // 5. 接口状态判断
    // http status header <200 || >299 let msg = "当前服务繁忙,请稍后再试" if (response.status === 404) { msg = "您访问的内容走丢了…" }
             Toast.info(msg,
    2) reject(HttpUtil.handleFailedResult({ fetchStatus: "error", netStatus: response.status, error: msg }, httpCustomerOpertion)) } }).catch(e => { const errMsg = e.name + " " + e.message reject(HttpUtil.handleFailedResult({ fetchStatus: "error", error: errMsg, netStatus: response.status }, httpCustomerOpertion)) }) } ).catch(e => { const errMsg = e.name + " " + e.message // console.error('ERR:', fetchUrl, errMsg) if (httpCustomerOpertion.isAbort) { // 请求超时后,放弃迟到的响应 return } if (isShowLoading) { HttpUtil.hideLoading() } httpCustomerOpertion.isFetched = true httpCustomerOpertion.isHandleResult && Toast.info("网络开小差了,稍后再试吧", 2) reject(HttpUtil.handleFailedResult({ fetchStatus: "error", error: errMsg }, httpCustomerOpertion)) }) }) return Promise.race([fetchPromise, HttpUtil.fetchTimeout(httpCustomerOpertion)]) }

      

      三、通用逻辑单独封装   

      代码里注释也非常详尽了,我就不一一赘述了

    /**
       * 统一处理后台返回的结果, 包括业务逻辑报错的结果
       * @param result
       * ps: 通过 this.isHandleError 来判断是否需要有fetch方法来统一处理错误信息
       */
      static handleResult(result, httpCustomerOpertion) {
        if (result.status && httpCustomerOpertion.isHandleResult === true) {
          const errMsg = result.msg || result.message || "服务器开小差了,稍后再试吧"
          const errStr = `${errMsg}(${result.status})`
          HttpUtil.hideLoading()
          Toast.info(errStr, 2)
        }
        return result
      }
      /**
       * 统一处fetch的异常, 不包括业务逻辑报错
       * @param result
       * ps: 通过 this.isHandleError 来判断是否需要有fetch方法来统一处理错误信息
       */
      static handleFailedResult(result, httpCustomerOpertion) {
        if (result.status && httpCustomerOpertion.isHandleResult === true) {
          const errMsg = result.msg || result.message || "服务器开小差了,稍后再试吧"
          const errStr = `${errMsg}(${result.status})`
          HttpUtil.hideLoading()
          Toast.info(errStr, 2)
        }
        const errorMsg = "Uncaught PromiseError: " + (result.netStatus || "") + " " + (result.error || result.msg || result.message || "")
        return errorMsg
      }
      /**
       * 控制Fetch请求是否超时
       * @returns {Promise}
       */
      static fetchTimeout(httpCustomerOpertion) {
        const { isShowLoading } = httpCustomerOpertion
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (!httpCustomerOpertion.isFetched) {
              // 还未收到响应,则开始超时逻辑,并标记fetch需要放弃
              httpCustomerOpertion.isAbort = true
              // console.error('ERR: 请求超时')
              if (isShowLoading) {
                HttpUtil.hideLoading()
              }
              Toast.info("网络开小差了,稍后再试吧", 2)
              reject({ fetchStatus: "timeout" })
            }
          }, httpCustomerOpertion.timeout || timeout)
        })
      }

      到此,fetch请求的封装就算完成了,如此封装的fetch请求方法可以兼容很多业务场景了。

      四、与后端接口的约定 

      当然,接口请求从来都不仅仅是前端一个人的事情,还需要跟后台的小伙伴进行约定。 比如:返回结果包含status, status = 0,说明业务逻辑正常,我们只管取结果即可; status!=0,说明业务逻辑异常,后端将异常信息放到msg字段里,前端将其Toast出来,展现给用户。

      

      五、封装后的调用方式

    export const fetchAction = (param, handleResult, handleFailResult) => {
      return HttpUtil.post(HttpApi.url, param).then( response => {
        handleResult(response.data)
      }).catch((e) => {
        handleFailResult()
        console.error(e)
      })
    }

    // 调用的结果就是这样的
    await
    fetchAction1

    await fetchAction2

    await fetchAction3
     

      结语

      Fetch方法的封装说到底就为了满足更多的业务场景,而且现实的业务场景中,我在上文中基本都已经提到了。也许你不能完全理解代码中所写的东西,但是你只要能理解为什么要做这些事情,你就知道该怎么去封装这些请求方法了。

  • 相关阅读:
    简单的StringBuffer实现
    Java-HashMap、HashSet、hashTable
    JavaScript 引用错误
    使用jconsole分析内存情况-JVM
    Thread 与 Runnable 混合使用测试
    裴波那序列-JAVA实现
    多线程之----------线程池
    winform 控件拖拽和缩放
    C# 使用Process调用外部程序中所遇到的参数问题
    winform textbox 的自动实现功能
  • 原文地址:https://www.cnblogs.com/warm-stranger/p/13229540.html
Copyright © 2011-2022 走看看