項目實戰(zhàn)之接口處理篇~一文搞定接口請求

在項目開發(fā)中,接口請求是必不可少的,為了方便使用和維護,大家都會將接口請求的方法二次封裝。下面小編將我項目中接口封裝使用的方法分享給大家,希望可以幫到大家。喜歡的給個三連擊再走喲。

目前前端常用的請求方式主要有兩種:axios、Fetch。下面小編就這兩種給大家詳細的介紹介紹。
axios

axios 是一個基于Promise 用于瀏覽器和 nodejs 的 HTTP 客戶端,本質(zhì)上也是對原生XHR的封裝,只不過它是Promise的實現(xiàn)版本,符合最新的ES規(guī)范。
特點

    從瀏覽器中創(chuàng)建 XMLHttpRequest
    支持 Promise API
    客戶端支持防止CSRF
    提供了一些并發(fā)請求的接口(重要,方便了很多的操作)
    從 node.js 創(chuàng)建 http 請求
    攔截請求和響應(yīng)
    轉(zhuǎn)換請求和響應(yīng)數(shù)據(jù)
    取消請求
    自動轉(zhuǎn)換JSON數(shù)據(jù)

兼容性問題
axios在PC端瀏覽器的兼容性問題

axios支持IE8+,但原理是基于promise之上實現(xiàn)的,因此會存在不兼容IE的問題。

trident內(nèi)核的瀏覽器下會報: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在安卓低版本兼容性處理

在較低版本的安卓手機中發(fā)現(xiàn)發(fā)現(xiàn)封裝的axios請求無效,主要原因還是低版本的安卓手機無法使用promise

解決方式:

    安裝 es6-promise

npm  install  es6-promise -s

 

    引入注冊es6-promise

一定要在axios注冊之前

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

promise.polyfill()

或者

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

 

二次封裝axios
設(shè)置baseUrl

axios.defaults.baseURL = baseUrl

 

設(shè)置超時時間

axios.defaults.timeout = 10000

 

請求攔截

axios提供了請求攔截,在這里我們可以做一些請求前的統(tǒng)一處理,比如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)
})

 

響應(yīng)攔截

axios提供了響應(yīng)攔截,在這里我們可以做一些響應(yīng)后的數(shù)據(jù)進行統(tǒng)一處理,比如報錯提示等。

const errorCode = {
  '000': '操作太頻繁,請勿重復(fù)請求',
  '401': '當(dāng)前操作沒有權(quán)限',
  '403': '當(dāng)前操作沒有權(quán)限',
  '404': '資源不存在',
  '417': '未綁定登錄賬號,請使用密碼登錄后綁定',
  '423': '演示環(huán)境不能操作,如需了解聯(lián)系冷冷',
  '426': '用戶名不存在或密碼錯誤',
  '428': '驗證碼錯誤,請重新輸入',
  '429': '請求過頻繁',
  '479': '演示環(huán)境,沒有權(quán)限操作',
  'default': '系統(tǒng)未知錯誤,請反饋給管理員',
  '40006':'登錄失效,請重新登錄'
}


// 響應(yīng)攔截
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('網(wǎng)絡(luò)請求失敗,請重試')
  }
  return Promise.reject(error)
})

 

全局進度條

在接口請求的時候,為了優(yōu)化用戶體驗,我們可以給接口增加一個全局的進度條。我習(xí)慣使用的是nprogress。

    安裝

npm i nprogress -S

 

    使用
    nprogress提供了兩個api:start()、done();分別是開始、結(jié)束,我們分別將這兩個方法加入到請求攔截器和響應(yīng)攔截器之中就可以了。

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)
})

// 響應(yīng)攔截
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('網(wǎng)絡(luò)請求失敗,請重試')
  }
  return Promise.reject(error)
})

 

使用封裝






為了方便使用,我們可以對axios的使用方法進行封裝,這樣便于在項目中使用,封裝方法如下:

const request = function ({ url, params, config, method }) {
  // 如果是get請求 需要拼接參數(shù)
  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

 

這樣我們使用的時候,只需要傳對應(yīng)的參數(shù)就可以了。
使用示例

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 實現(xiàn),支持 async/await
    脫離了XHR,是ES規(guī)范里新的實現(xiàn)方式
    更加底層,提供的API豐富(request, response)
    使用isomorphic-fetch可以方便同步

劣勢

    fetch是一個低層次的API,你可以把它考慮成原生的XHR,所以使用起來并不是那么舒服,需要進行封裝
    fetch只對網(wǎng)絡(luò)請求報錯,對400,500都當(dāng)做成功的請求,服務(wù)器返回 400,500 錯誤碼時并不會 reject,只有網(wǎng)絡(luò)錯誤這些導(dǎo)致請求不能完成時,fetch 才會被 reject
    fetch默認不會帶cookie,需要添加配置項: fetch(url, {credentials: ‘include’})
    fetch不支持abort,不支持超時控制,使用setTimeout及Promise.reject的實現(xiàn)的超時控制并不能阻止請求過程繼續(xù)在后臺運行,造成了流量的浪費
    fetch沒有辦法原生監(jiān)測請求的進度,而XHR可以

兼容性問題

fetch是相對比較新的技術(shù),因此也就會存在一些瀏覽器兼容問題,接下來我們借can i use的一張圖說明fetch在各大瀏覽器的兼容性。

 

兼容低版本瀏覽器

fetch不兼容低版本瀏覽器是因為低版本瀏覽器不支持Proimse導(dǎo)致的。

解決方案: 手動寫個文件引入pinkie-promise,判斷當(dāng)前是否有window.Promise,如果沒有就把我引入的作為window.Promise.

步驟:

    安裝whatwg-fetch

npm install whatwg-fetch -save

 

    由于 whatwg-fetch模塊沒有pinkie-promise,因此需要在模塊內(nèi)引入

cd /node_modules/whatwg-fetch

npm install pinkie-fetch --save

 

    引入模塊

在fetch請求文件中引入模塊

import 'whatwg-fetch'

 

二次封裝

下面小編提供一個小編自己的封裝,是基于ts的fetch二次封裝。
定義常量

/**
 * 定義后端接口狀態(tài)碼
 */
const codeStatus = {
    _SUCCESS_:'200',//請求成功
    _TOKEN_INVALID_:'40006',//token失效
}
const codeMessage:any = {
    200: '服務(wù)器成功返回請求的數(shù)據(jù)。',
    201: '新建或修改數(shù)據(jù)成功。',
    202: '一個請求已經(jīng)進入后臺排隊(異步任務(wù))。',
    204: '刪除數(shù)據(jù)成功。',
    400: '發(fā)出的請求有錯誤,服務(wù)器沒有進行新建或修改數(shù)據(jù)的操作。',
    401: '用戶沒有權(quán)限(令牌、用戶名、密碼錯誤)。',
    403: '用戶得到授權(quán),但是訪問是被禁止的。',
    404: '訪問路徑錯誤',
    406: '請求的格式不可得。',
    410: '請求的資源被永久刪除,且不會再得到的。',
    422: '當(dāng)創(chuàng)建一個對象時,發(fā)生一個驗證錯誤。',
    500: '服務(wù)器發(fā)生錯誤,請檢查服務(wù)器。',
    502: '網(wǎng)關(guān)錯誤。',
    503: '服務(wù)不可用,服務(wù)器暫時過載或維護。',
    504: '網(wǎng)關(guān)超時。',
}

 

狀態(tài)碼處理

fetch 對所有的 response code 包括 200X 300X 400X 500X 等返回的都是 resolve 狀態(tài), 只有拋異常時才會走到 catch 里面,該函數(shù)把 非200X 請求作為 reject 狀態(tài)區(qū)分出來,在catch中統(tǒng)一處理。

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碼進行統(tǒng)一的處理
 * @param {*} response 后端返回的數(shù)據(jù)
 */
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時間,包括請求的連接、服務(wù)器處理及服務(wù)器響應(yīng)回來的時間
 * fetch的timeout即使超時發(fā)生了,本次請求也不會被abort丟棄掉,它在后臺仍然會發(fā)送到服務(wù)器端,只是本次請求的響應(yīng)內(nèi)容被丟棄而已
 * @param {Promise} fetch_promise    fetch請求返回的Promise
 * @param {number} [timeout=10000]   單位:毫秒,這里設(shè)置默認超時時間為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 的結(jié)果來傳入后續(xù)綁定的回調(diào)
    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的請求參數(shù),默認為空
 * @param {JSON} headers headers的參數(shù),默認為{}
 * @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,某些涉及用戶數(shù)據(jù)的接口需要在header中加上token
        ...headers
    };
    method = method.toUpperCase();
    let data = params
    let options:FetchOptions = {
        method: method,
        headers: header,
    }
    if(params != ''){   //如果網(wǎng)絡(luò)請求中帶有參數(shù)
        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";

/**
 * 請求服務(wù)器的函數(shù),用于請求數(shù)據(jù)
 * @param {*} params 請求數(shù)據(jù)時需要傳遞的參數(shù)
 */
export async function login(params: any) {
  return BaseFetch({
    url: 'login',
    method: "POST",
    params
  });
}

 


歡迎關(guān)注微信公眾號:猴哥說前端