Axios取消请求CancelToken

22,456 阅读2分钟

项目中遇到的场景,change 事件触发模糊匹配时,短时间内发送多个 ajax 请求,最后一次返回的数据可能不是最后一次触发 change 时的请求,导致获取数据不匹配

解决方案

使用的 Axios 做数据请求,使用 cancel token 取消请求

官方案例 github.com/axios/axios


    // using the CancelToken.source factory 
    const CancelToken = axios.CancelToken 
    const source = CancelToken.source()
    
    // get
    axios.get('/user/1', {
        cancelToken: source.token
    }).catch(function (thrown) {
        if(axios.isCancel(thrown)) {
            console.log('Request canceled', thrown.message)
        } else {
            // handle error
        }
    })
    
    // post
    axios.post('/user/1', {
        name: ''
    }, {
        cancelToken: source.token
    })
    
    // cancel request 参数可选
    source.cancel('取消上次请求')


    // use executor function
    const CancelToken = axios.CancelToken
    let cancel
    
    // get
    axios.get('/user/1', {
        cancelToken: new CancelToken(function executor(c) {
            // executor 函数接收一个 cancel 函数作为参数
            cancel = c
        })
    })
    
    // post
    axios.post('/user/1', {
        name: ''
    }, {
        cancelToken: new CancelToken(function executor(c) {
            cancel = c
        })
    })
    
    // cancel request
    cancel()

我的 Vue 项目实例

    import axios from 'axios'
    let cancel
    let CancelToken
    
    mounted() {
        CancelToken = axios.CancelToken
    }
    
    // 多次触发fetchList请求 取消上次请求,触发最新请求
    async fetchList() {
        if(cancel) {
            cancel()
        }
        await axios.post('/user/list', {
            query: ''
        }, {
            cancelToken: new CancelToken(function executor(c) {
                cancel = c
            })
        })
    }

原生XHR

原生的 XHR 对象是调用 abort()方法取消 ajax 请求

    
    let xhr
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest()
    } else {
      xhr = new ActiveXObject('Microsoft.XMLHTTP')
    }
    xhr= new XMLHttpRequest()
    xhr.open('GET', 'https://api')
    xhr.send()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        // success
      } else {
        // error
      }
    }
    // 取消ajax请求 readyState = 0
    xhr.abort()

Axios 源码轻解析 CancelToken

axios/lib/cancel/CancelToken.js


    'use strict';

    var Cancel = require('./Cancel');
    
    function CancelToken(executor) {
        if (typeof executor !== 'function') {
            throw new TypeError('executor must be a function.');
        }
        /**
        * 定义 resolvePromise
        * 新建promise实例
        * 将 promise的resolve方法赋值给 resolvePromise 目的是为了在promise对象外使用resolvePromise方法来改变对象状态
        */
        var resolvePromise;
        this.promise = new Promise(function promiseExecutor(resolve) {
            resolvePromise = resolve;
        });
        /**
        * 将CancelToken实例赋值给token
        * 给executor传入cancel方法,cancel可调用resolvePromise方法
        */
        var token = this;
        executor(function cancel(message) {
            if (token.reason) {
                // 取消已响应 返回
                return;
            }
            token.reason = new Cancel(message);
            // 这里执行的就是promise的resolve方法,改变状态
            resolvePromise(token.reason);
      });
    }
    
    CancelToken.prototype.throwIfRequested = function throwIfRequested() {
        if (this.reason) {
            throw this.reason;
        }
    };
    
    
    CancelToken.source = function source() {
        var cancel;
        var token = new CancelToken(function executor(c) {
            // c 就是CancelToken中给executor传入的cancel方法
            cancel = c;
        });
        return {
            token: token,
            cancel: cancel
        };
    };
    
    module.exports = CancelToken;

执行 promise.resolve() 后如何取消 ajax 请求

  1. CancelToken 添加到 axiosCancelToken属性上
    // axios/lib/axios.js
    
    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel');
  1. CancelTokenresolve 的方法触发 promise.then 方法
    // axios/lib/adapters/xhr.js
    
    // 创建XHR对象
    var request = new XMLHttpRequest()
    // 模拟当前ajax请求
    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true)
    
    if (config.cancelToken) {
        config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
                return;
            }
            // 取消ajax请求
            request.abort();
            reject(cancel);
            // Clean up request
            request = null;
        });
    }