'use strict';
var Cancel = require('./Cancel');
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken( function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
function CancelToken(executor) {
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(cancel);
function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
}
}
背景
在公司的一个项目中,最近频繁出现一个bug,点餐的过程中,不同桌台之间的数据老是串数据,最后发现是由于服务员在不同桌台之间快速切换导致的。如果网络环境不好,前几个桌台的订单数据还没回来,后几个桌台的数据已经发出,并且在前端,订单数据是封装成了一个OrderService进行管理的。也就是说前端只保存一份订单的数据。解决这个问题的方案有很多。但是改动最小又最友好的方式,应该是属于“取消请求”的方案,从A桌台切到B桌台的时候,把A桌台的请求取消。axios自带取消请求的功能,但是由于此项目使用的是angularjs,请求的service是自己封装的。所以,只能自己动手改造一下请求的service了。在改动之前,扒拉了一下axios的源码,看看axios是怎么实现的"取消请求"的。
axios如何使用取消请求
首先通过执行source函数创建一个source, 然后把source.token 通过参数的配置传给axios。最后执行source上的cancel方法。
axios相关源码分析
相关的源码在这里:https://github.com/axios/axios/tree/503418718f669fcc674719fd862b355605d7b41f/lib/cancel
既然首先是通过CancelToken上的source函数创建一个source。那我们首先就分析一下CancelToken是什么鬼?
源码如下:
可以看到CancelToken是一个构造函数,这个构造函数上挂载一个静态方法source。我们一个个的分析。 首先分析CancelToken这个构造函数,删繁就简,省略了不太重要的判断之后,这个函数如下,可以看到这个构造函数。
然后我们再看一下source方法。 source方法通过new CancelToken(executor) 创建一个实例 token。并返回一个对象。这个对象上挂账两个属性,一个是token,一个是cancel。 这个cancel是token内部的那个cancel,可以触发token内部的那个promise变成完成态。
因此,到这里我们也基本上能猜到axios是怎么取消一个请求的。通过source工厂函数,返回一个token和token内部的cancel,调用cancel的时候能触发token内部的promise变成完成态。当token内部的promise是完成态的时候,此时触发请求的完成,不管结果回来还是没回来。结果如我们预期的一样。看下面的xhr相关的代码
https://github.com/axios/axios/blob/503418718f669fcc674719fd862b355605d7b41f/lib/adapters/xhr.js
此时当参数配置中传入config.cancelToken的话,cancelToken也就是我们生成的token,我们利用cancelToken上的promise,调用cancelToken上的promise的then方法,给此promise放入一个注册函数。此函数执行的时候会调用reject触发这个请求变成失败的完成态。
总结
利用一个工厂函数创建一个对象,对象上挂载着promise,和让这个promise变成完成态的cancel方法。由于这里面有很多promise,为了不混淆,我们暂且叫这个promise为p1。 p1将来会通过参数配置的方式传入到另一个promise内部。这个promise我们称为p2. p2 是封装的ajax的promise。如果p2成fullfilled状态或者是rejected状态。这个请求就会结束。所以,我们从外部执行cancel方法。导致p1变成完成态,进而触发p2变成rejected状态。来结束这个请求。即取消请求。
ok。that‘s all。其实,这么一分析还是非常简单的。我们可以利用这个思路,去给自己封装的请求函数提供取消请求的功能。