关于axios:axios的cancelToken

39次阅读

共计 5351 个字符,预计需要花费 14 分钟才能阅读完成。

axios 的 config 中提供了一个 cancelToken 的属性来勾销申请。

用法

用法一

利用 CancelToken.source 办法,它会返回带有 CancelToken 实例和 cancel 办法的对象。将 CancelToken 实例赋值给 config.cancelToken。在须要勾销申请时调用 cancel 办法就会勾销带有与这个 cancel 绝对应的 token 的所有申请。

var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {//get 申请在第二个参数
    cancelToken: source.token
}).catch(function(thrown) {});
axios.post('/user/12345', {//post 申请在第三个参数
    name: 'new name'
}, {cancelToken: source.token});
source.cancel('不想申请了');

用法二

通过实例化一个 CancelToken 对象,并传入一个回调函数,赋值 cancel 办法。

var CancelToken = axios.CancelToken;
var cancel;
axios.get('/user/12345', {cancelToken: new CancelToken((c)=>cancel=c)// 实例化一个 CancelToken 对象, 赋值 cancel 办法
}).catch(function(thrown) {});
cancel()

第一种用法只是在第二种用法上多包了一层,实质差不多

原理剖析

// 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.');
    }
    /**
    * 定义一个未来能执行勾销申请的 promise 对象,当这个 promise 的状态为实现时 (fullfilled),
    * 就会触发勾销申请的操作(执行 then 函数)。而执行 resolve 就能将 promise 的状态置为实现状态。* 这里把 resolve 赋值给 resolvePromise,就是为了在这个 promise 外能执行 resolve 而扭转这个 promise 的状态
    * 留神这个 promise 对象被赋值给 CancelToken 实例的属性 promise,未来定义 then 函数就是通过这个属性失去 promise
    */
    var resolvePromise;
    this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve;});
    /**
    * 将 CancelToken 实例赋值给 token
    * 执行 executor 函数,将 cancel 办法传入 executor,* 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;}
};

// 这里能够看清楚 source 函数的真面目
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;

CancelToken

CancelToken 是一个构造函数,通过 new CancelToken() 失去的是一个实例对象,创立时它只有一个属性 promise, 它的值是一个能触发勾销申请的 Promise 对象。

token = new CancelToken(executor function) ===> {promise: Promise 对象}

执行 CancelToken 函数做了两件事:

创立一个 Promise 对象,且将这个对象赋值给 promise 属性,其 resolve 参数被裸露进去以备内部援用。
执行 executor 函数,将外部定义的 cancel 函数作为参数传递给 executor

var token = this;
var cancel = function (message) {if (token.reason) {
        // 勾销已响应 返回
        return;
    }
    token.reason = new Cancel(message);
    // 这里执行的就是 promise 的 resolve 办法,扭转状态
    resolvePromise(token.reason);
}
executor(cancel);

当执行

let cancel
token = new CancelToken(function executor(c) {cancel = c;});

token 值为 {promise: Promise 对象}
cancel 就是 CanelToken 外部的那个 cancel 函数。

CancelToken.source

CancelToken.source 是一个函数,通过源码能够看到,执行 const source = CancelToken.source(), 失去的是一个对象:

return {
    token: token,
    cancel: cancel
};

蕴含一个 token 对象,即 CancelToken 实例对象,和一个 cancel 函数。因而 CancelToken.source() 函数的作用是生成 token 对象和获得 cancel 函数。token 对象是用于配置给 axios 申请中的 cancelToken 属性,cancel 函数是未来触发勾销申请的函数。

config.cancelToken.promise

在 cancelToken.promise 的回调中调用 abort 停止申请

if (config.cancelToken) {
    // Handle cancellation
    config.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return;}
        request.abort();//xmlHttpRequest 对象的 abort 停止申请
        reject(cancel);
        // Clean up request
        request = null;
    });
}

Cancel

Cancel 用来存储 cancel 办法传入的勾销起因

executor(function cancel(message) {//cancel 传入起因
        if (token.reason) {
            // 勾销已响应 返回
            return;
        }
        token.reason = new Cancel(message);token.reason 存储起因
        // 这里执行的就是 promise 的 resolve 办法,扭转状态
        resolvePromise(token.reason);
  });
//Cancel.js
'use strict';

/**
 * A `Cancel` is an object that is thrown when an operation is canceled.
 *
 * @class
 * @param {string=} message The message.
 */
function Cancel(message) {this.message = message;}

Cancel.prototype.toString = function toString() {return 'Cancel' + (this.message ? ':' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

axios.isCancel()

axios.isCancel 来判断该申请是不是通过 cancel 手动勾销的

'use strict';

module.exports = function isCancel(value) {return !!(value && value.__CANCEL__);
};

cancelToken 用法实战

问题

一个聊天我的项目遇到这样的一个问题,客户在用户列表中切换用户,每次切换都会去获取用户的根本信息和之前的聊天记录并渲染到页面上。如果频繁切换,可能导致页面要响应很多申请,并且如果前一次切换的申请在最初一次切换申请前面返回还会导致页面上渲染的数据不对。

思考

尽管能够通过防抖的形式解决,然而每次切换提早一段时间用户体验又不好,所以想到了应用 cancelToken 的形式来解决。

基本思路

1、每次申请生成一个 cancelToken,也能够本人传入 cancelToken。而后依据 url 和 method,将对应的 cancel 办法存起来
2、当有同样的申请过去后,通过 url 和 method 拿出 cancel 办法并调用,这样就能够勾销前一次还没返回的雷同申请,页面也不会响应。保障页面一段时间内只会响应最初一次反复申请。

相干代码

//cancelToken.js
// 申明一个 Map 用于存储每个申请的标识 和 勾销函数
const pending = new Map()
/**
 * 增加申请
 * @param {Object} config 
 */
export const addPending = (config) => {
  const url = [
    config.method,
    config.url
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {if (!pending.has(url)) { // 如果 pending 中不存在以后申请,则增加进去
      pending.set(url, cancel)
    }
  })
}
/**
 * 移除申请
 * @param {Object} config 
 */
export const removePending = (config) => {
  const url = [
    config.method,
    config.url
  ].join('&')
  if (pending.has(url)) { // 如果在 pending 中存在以后申请标识,须要勾销以后申请,并且移除
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}
/**
 * 清空 pending 中的申请(在路由跳转时调用)*/
export const clearPending = () => {for (const [url, cancel] of pending) {cancel(url)
  }
  pending.clear()}
import {addPending,removePending} from "./cancelToken"// 引入 cancelToken
// 创立 axios 实例
const service = axios.create({
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-Requested-With': 'XMLHttpRequest'
  }
})

// request 拦截器
service.interceptors.request.use(config => {removePending(config) // 在申请开始前,对之前的申请做查看勾销操作
  addPending(config) // 将以后申请增加到 pending 中
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone 拦截器
service.interceptors.response.use(
  response => {removePending(response) // 在申请完结后,移除本次申请
    Promise.relove(response)
  },
  error => {if (axios.isCancel(error)) {// 解决手动 cancel
      console.log('这是手动 cancel 的')
    }
    return Promise.reject(error)
  }
)

正文完
 0