Axios -- cancelToken取消请求

官网使用方式如下:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

axios.CancelToken是一个函数,source() 是该函数静态方法(就是直接挂在函数上,通过函数名访问的),代码如下:

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

当调用source()方法后,生成了CancelToken对象,注意传递的 executor函数,以及该函数的参数cc可以控制CancelToken函数内部的执行,将c赋值给了外部变量,那么也就是将CancelToken函数内部的执行状态交由外部控制

看看CancelToken函数做了哪些事:

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

  this.promise = new Promise(function promiseExecutor(resolve) {
  	// 同样创建一个promise,并将其状态改变交由外部控制
    resolvePromise = resolve;
  });

  // token 即为构造后的实例对象
  var token = this;

  // 先给该promise添加一个then回调方法
  this.promise.then(function(cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;
	// 当 resolvePromise 执行时,先将内部的回调函数队列清空
	// 至于这个队列怎么继续添加 then 回调函数,在 适配器 中
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
    token._listeners = null;
  });
  // 重写该promise的then方法
  this.promise.then = function(onfulfilled) {
    var _resolve;
    // 相当于包装了一层,添加了“订阅”处理
    // 会先将“订阅”的回调函数执行,(上面会将 cancel 作为“订阅”函数的参数传入执行)
    // “订阅”函数(也就是此处的 resolve)执行后,接着 then,调用外部传入的回调函数
    var promise = new Promise(function(resolve) {
      token.subscribe(resolve);
      _resolve = resolve;
    }).then(onfulfilled);

    promise.cancel = function reject() {
      token.unsubscribe(_resolve);
    };

    return promise;
  };
  // executor 就是 source() 方法中的 executor;
  // cancel 就是 c
  // 当外部手动调用 source.cancel(message) 时
  // **resolvePromise 执行,整个CancelToken函数内部的 promise 链也就执行了**
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

再看一眼使用方式:

// post() 方法的第二个参数是 body,第三个参数是 options
axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消请求
source.cancel('Operation canceled by the user.');
它是怎么将请求取消的呢?

适配器

在浏览器端创建XMLHttpRequest实例发送网络请求,在Node中使用平台内置的网络模块,主要看CancelToken相关内容

// section1
...
var onCanceled;
// done 函数用于正常接受请求结束后,取消“订阅”的回调函数
function done() {
  if (config.cancelToken) {
    config.cancelToken.unsubscribe(onCanceled);
  }

  if (config.signal) {
    config.signal.removeEventListener('abort', onCanceled);
  }
}

// 订阅的回调函数在适配器发出请求时添加
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
  if (!request) {
    return;
  }
  reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);
  request.abort(); // XMLHttpRequest自带的取消请求方法
  request = null;
};

config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
  config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}

if (!requestData) {
requestData = null;
}

// Send the request
request.send(requestData);
...

所以,当在外部手动调用source.cancel(message)方法时,下列代码便会执行(也就是CancelToken函数体内的代码)

// 先给该promise添加一个then回调方法
  this.promise.then(function(cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;
	// 当 resolvePromise 执行时,先将内部的回调函数队列清空
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel); // 队列中的回调函数依次执行
    }
    token._listeners = null;
  });
那么又是如何判断source.cancel(message)方法已经被调用了呢?

dispatchRequest.js

function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new Cancel('canceled');
  }
}

module.exports = function dispatchRequest(config) {
  // 
  throwIfCancellationRequested(config);

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    ...
  	}, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);
      ...
    }

    return Promise.reject(reason);
  });
  ...
}

throwIfCancellationRequested函数的作用就是判断是否使用了cancelToken,然后调用CancelToken原型上的抛出错误的方法

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

可以看到if (this.reason)存在,就抛出异常,那么reason又是怎么添加的呢?
又回到CancelToken函数体中

executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
	
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });

可知,cancel 函数执行,就添加了 reason,而cancel执行就是source.cancel()

小结

  • 取消请求是在网络请求返回之前执行有效(当然啦,这不是废话嘛)
  • 手动取消请求,是通过将CancelToken函数体内的执行情况交由外部控制(使用promise,暴露resolve方法),然后将“取消请求”的状态穿入到发出请求的函数dispatchRequest中去截获状态
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值