axios切換路由取消指定請求與取消重複請求並存方案

前言

關於axios取消請求的文章和資料不勝其數。大部分都是單獨介紹 切換路由時取消上個頁面還沒有完成的請求 或者 取消重複請求 的文章,且大部分是介紹了核心的內容,缺少關於一整套完整的方案的描述。javascript

此文章介紹這麼一套完整方案,便可知足切換路由取消指定請求取消重複請求,並附帶一些其餘小乾貨處理前端

請求攔截

關於axios取消請求,官方文檔也有簡單描述到使用方法,基礎方法我這裏不贅述,直接寫方案。vue

思路:java

用一個變量存儲目前處於pending狀態的請求,用一個標識代表。攔截髮送請求,判斷這個api請求以前是否已經有還在pending的同類,便是否存在上述變量中,若是存在,則取消處理,不存在就正常發送,等請求完結後刪除這個api請求在上述變量中的標識,這是一個完整的處理取消重複請求的流程。ios

而對於切換了路由(頁面),要取消上個頁面仍然pending的請求,就須要監聽路由切換,每次切換都對上述變量存儲的請求標識作判斷,哪些是要取消的請求,就給取消掉便可。這裏並無對全部處於pending狀態的請求作取消處理,緣由是,系統中可能有些請求是沒必要要因爲切換路由就要取消的,如全局的一些請求等。固然,這個也能夠延伸一下,用於你實際項目需求,指定哪些所須要取消。vue-router

關於請求效果的設置,我習慣單獨一個文件http.js,代碼註釋一步一步寫得很清楚,你們看下去就天然很好理解了。axios

// http.js

import axios from 'axios';

// 用於存儲目前狀態爲pending的請求標識信息
let pendingRequest = [];

/** * 請求的攔截處理 * @param config - 請求的配置項 */
const handleRequestIntercept = config => {
    // 區別請求的惟一標識,這裏用方法名+請求路徑
    // 若是一個項目裏有多個不一樣baseURL的請求
    // 能夠改爲`${config.method} ${config.baseURL}${config.url}`
    const requestMark = `${config.method} ${config.url}`;
    // 找當前請求的標識是否存在pendingRequest中,便是否重複請求了
    const markIndex = pendingRequest.findIndex(item => {
        return item.name === requestMark;
    });
    // 存在,即重複了
    if (markIndex > -1) {
        // 取消上個重複的請求
        pendingRequest[markIndex].cancel();
        // 刪掉在pendingRequest中的請求標識
        pendingRequest.splice(markIndex, 1);
    }
    // (從新)新建針對此次請求的axios的cancelToken標識
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    config.cancelToken = source.token;
    // 設置自定義配置requestMark項,主要用於響應攔截中
    config.requestMark = requestMark;
    // 記錄本次請求的標識
    pendingRequest.push({
        name: requestMark,
        cancel: source.cancel,
        routeChangeCancel: config.routeChangeCancel // 可能會有優先級高於默認設置的routeChangeCancel項值
    });
    
    return config;
};

/** * 響應的攔截處理 * @param config - 請求的配置項 */
const handleResponseIntercept = config => {
    // 根據請求攔截裏設置的requestMark配置來尋找對應pendingRequest裏對應的請求標識
    const markIndex = pendingRequest.findIndex(item => {
        return item.name === config.requestMark;
    });
    // 找到了就刪除該標識
    markIndex > -1 && pendingRequest.splice(markIndex, 1);
}

/** * 建立axios實例 * @param {String} url - 訪問後臺的主url */
const createAxiosInstance = (baseUrl) => {
    let instance = axios.create({
        baseURL: baseUrl
    });
    // 默認把請求視爲切換路由就會把pending狀態的請求取消,false爲不取消
    instance.defaults.routeChangeCancel = true;
    
    // 請求攔截
    instance.interceptors.reqeust.use(handleRequestIntercept, error => Promise.reject(error));
    
    // 響應攔截
    instance.interceptors.response.use(res => {
        handleResponseIntercept(res.config);
        // 其實更多狀況下你執行獲取res.data
        // 能夠return res.data;
        return res;
    }, error => {
        let errorFormat = {};
        const response = error.response;
        // 請求已發出,但服務器響應的狀態碼不在 2xx 範圍內
        if (response) {
            handleResponseIntercept(response.config);
            // 設置返回的錯誤對象格式(按照本身項目實際需求)
            let errorFormat = {
                status: response.status,
                data: response.data
            };
        }
        // 若是是主動取消了請求,作個標識
        if (axios.isCancel(error)) {
            errorFormat.selfCancel = true;
        }
        // 其實還有一個狀況
        // 在設置引起錯誤的請求時,error.message纔是錯誤信息
        // 但我以爲這個通常是腳本錯誤,咱們項目提示也不該該提示腳本錯誤給用戶看,通常都是咱們自定義一些默認錯誤提示,如「建立成功!」
        // 因此這裏不針對此狀況作處理。
        
        return Promise.reject(errorFormat);
    });
    
    // 還有一些其餘你想要的axios實例設置
    // ...
    
    return instance;
}

// 其餘配置
// ...

export {
    pendingRequest
}
複製代碼

上面的代碼裏就能實現取消重複請求了,那麼要實現切換路由後取消pending的指定請求,就須要在監聽路由的變化了。api

在路由設置文件裏,這裏以vue-router爲例子性能優化

// router.js

import { pendingRequest } from 'http.js';

// ...這是其餘配置

router.beforeEach((to, from, next) => {
    // 把上個頁面還沒結束的請求取消掉
    pendingRequest.forEach(item => {
        item.routeChangeCancel && item.cancel();
    });
    // ... 其餘處理
});
複製代碼

實際項目應用時

處理報錯

上面咱們定好了怎麼作攔截,怎麼處理路由切換,基本上已經處理好了。bash

還有一些小點,還記得咱們對響應攔截作了判斷是不是主動取消,而後設置了selfCancel標識。

這個有什麼用呢?是用在咱們在項目中發請求時,捕獲錯誤信息時作處理的。如

// 我建立了axios實例並綁在Vue的http上
Vue.http.get('/api/test').then(res => {
    // 成功請求的處理
}).catch(e => {
    // 這裏就要判斷是不是主動取消請求的
    e.selfCancel || this.$message.error('請求失敗!');
})
複製代碼

這裏的this.$message.error主要是用來提示錯誤信息給用戶看的,而因爲咱們主動取消了請求,會走到catch流程中,可是這實際意義上並不算一個錯誤,不該該告訴用戶出現錯誤了。

因此這個selfCancel標識就是用來告訴開發者這是咱們主動取消請求致使的報錯,不該該提示給用戶看

區分是否頁面請求

咱們上面經過請求的config.routeChangeCancel來判斷切換路由時是否取消正在pending的請求。

咱們是統一在建立axios實例時,就設置默認routeChangeCancel爲true,通常什麼狀況下是true呢? 咱們切換路由每每就是想把上個頁面還沒有完成的請求給取消掉,避免因爲切換了路由請求完成了回調函數處理時若是有涉及上個頁面的變化或方法或dom對象等,如今沒有了會報腳本錯等問題。固然這也是個性能優化的處理,提高用戶交互體驗,因此這個是有必要的。

有的人可能以爲,只要切換了路由就把目前全部還沒有完成的請求取消不就行了嗎?可是你的系統裏或許有些公共請求,不受且不該受頁面的切換而受到影響的請求,這類請求就不該該在切換路由的時候給取消掉了!常見的例子是位於系統頭部的某些信息是須要發請求獲取的,切換頁面頭部header也是不會變的對吧。

那麼咱們已經經過instance.defaults.routeChangeCancel = true設置了,那麼接下來怎麼設置才能知道哪些請求不須要切換路由就取消呢?

Vue.http.get('/api/test', {
    routeChangeCancel: false
})
複製代碼

在實例發送請求的時候,這麼設置就能覆蓋了

其實若是不想以這種設置的形式去區分,其實也能夠建立兩種axios實例,一種表示切換路由就要取消的,另外一種就是不須要取消的。可是我以爲,這種不須要取消的請求在一個項目中,一個系統中較少,其實經過這種配置的形式靈活性更好,處理起來也不會不少很麻煩。

總結

關於取消請求的場景就介紹到這裏了。

未經容許,請勿私自轉載

始發於 K前端

相關文章
相關標籤/搜索