关于axios:axios的cancelToken

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理