项目实战之接口处理篇~一文搞定接口请求

在项目开发中,接口请求是必不可少的,为了方便使用和维护,大家都会将接口请求的方法二次封装。下面小编将我项目中接口封装使用的方法分享给大家,希望可以帮到大家。喜欢的给个三连击再走哟。

目前前端常用的请求方式主要有两种:axios、Fetch。下面小编就这两种给大家详细的介绍介绍。

axios

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。

特点

  • 从浏览器中创建 XMLHttpRequest
  • 支持 Promise API
  • 客户端支持防止CSRF
  • 提供了一些并发请求的接口(重要,方便了很多的操作)
  • 从 node.js 创建 http 请求
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据

兼容性问题

axios在PC端浏览器的兼容性问题

axios支持IE8+,但原理是基于promise之上实现的,因此会存在不兼容IE的问题。

trident内核的浏览器下会报:vuex requires a Promise polyfill in this browser

解决方式:

  • 安装 babel-polyfill
npm  install  babel-polyfill -s
  • 安装成功以后需要在 main.js 中引入 babel-polyfill

一般会配置 webpack.base.config.js 中 entry

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: ["babel-polyfill", "./src/main.js"] 
    // app: './src/main.js'
  },
}

axios在安卓低版本兼容性处理

在较低版本的安卓手机中发现发现封装的axios请求无效,主要原因还是低版本的安卓手机无法使用promise

解决方式:

  • 安装 es6-promise
npm  install  es6-promise -s
  • 引入注册es6-promise

一定要在axios注册之前

// 注意: es6-promise   一定要在 axios 之前注册

promise.polyfill()

或者

require('es6-promise').polyfill();

二次封装axios

设置baseUrl
axios.defaults.baseURL = baseUrl
设置超时时间
axios.defaults.timeout = 10000
请求拦截

axios提供了请求拦截,在这里我们可以做一些请求前的统一处理,比如token等。

// 请求拦截
axios.interceptors.request.use((config) => {
  NProgress.start()
  let token = sessionStorage.token
  if (token) {
    config.headers.x_access_token = token
  }
  return config
}, function (error) {
  return Promise.reject(error)
})
响应拦截

axios提供了响应拦截,在这里我们可以做一些响应后的数据进行统一处理,比如报错提示等。

const errorCode = {
  '000': '操作太频繁,请勿重复请求',
  '401': '当前操作没有权限',
  '403': '当前操作没有权限',
  '404': '资源不存在',
  '417': '未绑定登录账号,请使用密码登录后绑定',
  '423': '演示环境不能操作,如需了解联系冷冷',
  '426': '用户名不存在或密码错误',
  '428': '验证码错误,请重新输入',
  '429': '请求过频繁',
  '479': '演示环境,没有权限操作',
  'default': '系统未知错误,请反馈给管理员',
  '40006':'登录失效,请重新登录'
}


// 响应拦截
axios.interceptors.response.use(function (response) {
  const status = Number(response.data.code) || 200
  const msg = response.data.message || errorCode[status] || errorCode['default']
  if (response.data.code === 40006) {//token失效
    //......
    return Promise.reject(msg)
  }
  if (response.status !== 200 || response.data.code !== 200) {//接口报错,提示错误信息
    message.error(msg);
    return Promise.reject(msg)
  }
  return response
}, function (error) {
  if (axios.isCancel(error)) {
    requestList.length = 0
    throw new axios.Cancel('cancel request')
  } else {
    message.error('网络请求失败,请重试')
  }
  return Promise.reject(error)
})
全局进度条

在接口请求的时候,为了优化用户体验,我们可以给接口增加一个全局的进度条。我习惯使用的是nprogress。

  • 安装
npm i nprogress -S
  • 使用
    nprogress提供了两个api:start()、done();分别是开始、结束,我们分别将这两个方法加入到请求拦截器和响应拦截器之中就可以了。
import NProgress from 'nprogress' // 引入nprogress插件
import 'nprogress/nprogress.css'  // 这个nprogress样式必须引入

// 请求拦截
axios.interceptors.request.use((config) => {
  NProgress.start()
  let token = sessionStorage.token
  if (token) {
    config.headers.x_access_token = token
  }
  return config
}, function (error) {
  return Promise.reject(error)
})

// 响应拦截
axios.interceptors.response.use(function (response) {
  NProgress.start()
  const status = Number(response.data.code) || 200
  const msg = response.data.message || errorCode[status] || errorCode['default']
  if (response.data.code === 40006) {//token失效
    //......
    return Promise.reject(msg)
  }
  if (response.status !== 200 || response.data.code !== 200) {//接口报错,提示错误信息
    message.error(msg);
    return Promise.reject(msg)
  }
  return response
}, function (error) {
  if (axios.isCancel(error)) {
    requestList.length = 0
    throw new axios.Cancel('cancel request')
  } else {
    message.error('网络请求失败,请重试')
  }
  return Promise.reject(error)
})
使用封装

为了方便使用,我们可以对axios的使用方法进行封装,这样便于在项目中使用,封装方法如下:

const request = function ({ url, params, config, method }) {
  // 如果是get请求 需要拼接参数
  let str = ''
  if (method === 'get' && params) {
    Object.keys(params).forEach(item => {
      str += `${item}=${params[item]}&`
    })
  }
  return new Promise((resolve, reject) => {
    axios[method](str ? (url + '?' + str.substring(0, str.length - 1)) : url, params, Object.assign({}, config)).then(response => {
      resolve(response.data)
    }, err => {
      if (err.Cancel) {
      } else {
        reject(err)
      }
    }).catch(err => {
      reject(err)
    })
  })
}
export default request

这样我们使用的时候,只需要传对应的参数就可以了。

使用示例

import request from '../../utils/axios'
import {api} from '../../utils/env'
export function login (params) {
  return request({
    url: api+'user/login',
    method: 'post',
    params: params
  })
}

Fetch

特点

  • 语法简洁,更加语义化
  • 基于标准 Promise 实现,支持 async/await
  • 脱离了XHR,是ES规范里新的实现方式
  • 更加底层,提供的API丰富(request, response)
  • 使用isomorphic-fetch可以方便同步

劣势

  • fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装
  • fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject
  • fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
  • fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
  • fetch没有办法原生监测请求的进度,而XHR可以

兼容性问题

fetch是相对比较新的技术,因此也就会存在一些浏览器兼容问题,接下来我们借can i use的一张图说明fetch在各大浏览器的兼容性。

兼容低版本浏览器

fetch不兼容低版本浏览器是因为低版本浏览器不支持Proimse导致的。

解决方案: 手动写个文件引入pinkie-promise,判断当前是否有window.Promise,如果没有就把我引入的作为window.Promise.

步骤:

  • 安装whatwg-fetch
npm install whatwg-fetch -save
  • 由于 whatwg-fetch模块没有pinkie-promise,因此需要在模块内引入
cd /node_modules/whatwg-fetch

npm install pinkie-fetch --save
  • 引入模块

在fetch请求文件中引入模块

import 'whatwg-fetch'

二次封装

下面小编提供一个小编自己的封装,是基于ts的fetch二次封装。

定义常量
/**
 * 定义后端接口状态码
 */
const codeStatus = {
    _SUCCESS_:'200',//请求成功
    _TOKEN_INVALID_:'40006',//token失效
}
const codeMessage:any = {
    200: '服务器成功返回请求的数据。',
    201: '新建或修改数据成功。',
    202: '一个请求已经进入后台排队(异步任务)。',
    204: '删除数据成功。',
    400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
    401: '用户没有权限(令牌、用户名、密码错误)。',
    403: '用户得到授权,但是访问是被禁止的。',
    404: '访问路径错误',
    406: '请求的格式不可得。',
    410: '请求的资源被永久删除,且不会再得到的。',
    422: '当创建一个对象时,发生一个验证错误。',
    500: '服务器发生错误,请检查服务器。',
    502: '网关错误。',
    503: '服务不可用,服务器暂时过载或维护。',
    504: '网关超时。',
}

状态码处理

fetch 对所有的 response code 包括 200X 300X 400X 500X 等返回的都是 resolve 状态, 只有抛异常时才会走到 catch 里面,该函数把 非200X 请求作为 reject 状态区分出来,在catch中统一处理。

const checkHttpStatus = (response:any) => {
    if (response.status >= 200 && response.status < 300) {
        return response;
    }
    const errortext = codeMessage[response.status] || response.statusText;
    //此处需要一个提示信息,待提示组件完成再完善
    BaseNormalToast(errortext,{duration:5000})
    const error = new Error(errortext);
    error.name = response.status;
    throw error;
};



/**
 * 对后端接口返回的code码进行统一的处理
 * @param {*} response 后端返回的数据
 */
const checkCodeStatus = (response:any) => {
    if(response.code == codeStatus._SUCCESS_){

    }else if(response.code == codeStatus._TOKEN_INVALID_){
        //token失效的一系列处理
        BaseNormalToast('token失效,请重新登录!!',{duration:5000})
    }else{//其他普通错误码
        BaseNormalToast(`错误码:${response.code},错误信息:${response.message}`,{duration:5000})
    }
    return response
};

超时处理
/**
 * 让fetch也可以timeout
 *  timeout不是请求连接超时的含义,它表示请求的response时间,包括请求的连接、服务器处理及服务器响应回来的时间
 * fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已
 * @param {Promise} fetch_promise    fetch请求返回的Promise
 * @param {number} [timeout=10000]   单位:毫秒,这里设置默认超时时间为10秒
 * @return 返回Promise
 */
function timeout_fetch(fetch_promise:any,timeout = 10000) {
    let timeout_fn:any = null; 
	//这是一个可以被reject的promise
	let timeout_promise = new Promise(function(resolve, reject) {
		timeout_fn = function() {
			reject('timeout promise');
		};
	});

	//这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
	let abortable_promise = Promise.race([
		fetch_promise,
		timeout_promise
	]);

	setTimeout(function() {
		timeout_fn();
	}, timeout);

	return abortable_promise ;
}

使用封装
/**
 * @param {string} url 接口地址
 * @param {string} method 请求方法:GET、POST
 * @param {JSON} [params=''] body的请求参数,默认为空
 * @param {JSON} headers headers的参数,默认为{}
 * @param {boolean} loading 是否需要全局的loading
 * @return 返回Promise 
 */
interface Options {
    url : string;
    method : string;
    params ?: any;
    headers ?: object;
}
interface FetchOptions {
    method : string;
    headers : any;
    body ?: any;
}
function BaseFetch(options:Options){
  let {url, method, params = '',headers = {}} = options
    let header = {
        Accept: 'application/json',
        "Content-Type": "application/json;charset=UTF-8",
        "x_access_token":sessionStorage.token,  //用户登陆后返回的token,某些涉及用户数据的接口需要在header中加上token
        ...headers
    };
    method = method.toUpperCase();
    let data = params
    let options:FetchOptions = {
        method: method,
        headers: header,
    }
    if(params != ''){   //如果网络请求中带有参数
        if(method=='GET'){
            let paramsArray:any[] = []
            Object.keys(params).forEach(key => paramsArray.push(key + '=' + encodeURIComponent(params[key])))
            if (url.search(/\?/) === -1) {
                typeof (params) === 'object' ? url += '?' + paramsArray.join('&') : url
            } else {
                url += '&' + paramsArray.join('&')
            }
        }
        if(method == 'POST' || method == 'PUT' || method == 'DELETE'){
            if (!(params instanceof FormData)) {
                data = JSON.stringify(params);
            } else {
                // params是formdata
                delete header["Content-Type"]
            }
            options =  {
                method: method,
                headers: header,
                body:data
            }
        }
        
    }
    return new Promise(function (resolve, reject) {
        timeout_fetch(fetch(url, options))
        .then(checkHttpStatus)
        .then((response) => response.json())
        .then(checkCodeStatus)
        .then((responseData) => {
            resolve(responseData);
        })
        .catch( (err) => {
            reject(err);
        });
    });
  
}

export default BaseFetch

使用示例

import { BaseFetch } from "./Fetch";

/**
 * 请求服务器的函数,用于请求数据
 * @param {*} params 请求数据时需要传递的参数
 */
export async function login(params: any) {
  return BaseFetch({
    url: 'login',
    method: "POST",
    params
  });
}

以上就是小编对接口请求的一些理解和封装,推荐使用axios。

小编后期会持续更新实战篇系列,想持续了解的,关注小编公众号【小猴子的web成长之路】

posted @ 2020-07-24 11:07  monkeySoft  阅读(587)  评论(0编辑  收藏  举报