【axios源码】请求取消的实现

一、如何设计一个取消请求的功能

取消请求是通过xhr.abort()这样实现的。但是不可能将xhr变量暴露出来,因此在ajax请求的内部必须有一个函数是取消请求的,通过这个函数取消请求达到隐藏xhr的目的。也可以直接是xhr.abort函数,显然这样就无法对取消请求的过程无法控制了。

function cancelRequest(){
    if(xhr==null){
        return
    }
    xhr.abort()
    reject("请求取消")
    xhr = null
}

接下来,这个函数怎么传递出去? 本来这个函数的控制权在封装的ajax函数里面,现在我们想把这个控制权转交给调用者。一种很巧妙的方式是通过函数传递出去,这要求初始化ajax请求时传过来一个函数去接收cancelRequest函数。

初始化时

var cancel = null
rquest({
   ...
  cancelToken:function(c){
      cancel = c
  }
 ...
})

封装时

function rquest(config){
...
    if(typeof config.cancelToken == "function"){
        config.cancelToken(cancelRequest)
    }
...
}

完整代码如下:

function xhr(config){
    return new Promise((resolve,reject)=>{
        var {url,data,headers,method,cancelToken} = config
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange=function(){
            if (xhr.readyState!=4){
                return
            }
            if (xhr.status === 0) {
                return;
            }
            if(xhr.status >= 200 && xhr.status < 300){
                //TODO::
                resolve(xhr.responseText)
            }else{
                reject("请求异常")
            }
            xhr = null
        }
        xhr.onabort = function(){
            if(!xhr){return}
            reject("请求取消");
            xhr = null
        }
        xhr.onerror = function(){reject("请求出错");xhr = null}
        xhr.ontimeout = function(){reject("请求超时");xhr = null}
        function cancelRequest(message){
            if(xhr==null){
                return
            }
            xhr.abort()
            reject(message)
            xhr = null
        }
        if(typeof cancelToken == "function"){
            cancelToken(cancelRequest)
        }
        xhr.open(method,url,true);
        xhr.send(data);
    })
}

这里有一点需要注意的是当请求取消的时候,status为0,而且首先触发的onreadystatechange函数,为了不让程序在这里reject,需要加上判断if (xhr.status === 0)。基本上取消请求的功能已经可以工作了,但是这么做有一个缺点,程序直接在onabort函数中reject出去了,无法包含请求取消的原因,因为xhr.abort()后面的reject无法工作。

二、请求分发

请求分发过程对取消请求的实现极为重要,细节见axios流程分析1.6节。前面那样实现的缺点
这个函数捕获到异常之后会判断是否是【请求取消】的异常,如果不是会尝试抛出【请求取消】的异常。而axios为取消请求封装的类中包含了这些功能。

三、axios取消请求解析

3.1 取消信息的封装,它包含可以识别出【取消请求】异常的属性__CANCEL__

function Cancel(message) {
  this.message = message;
}

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

Cancel.prototype.__CANCEL__ = true;

3.2 取消异常类型判断,对判断取消异常的封装

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

3.3 取消请求核心逻辑

在最开始,我为了将http请求内的abort函数传递出去,是给cancelToken赋予一个函数类型的值,并调用它,把cancel函数作为它的参数。理解取消请求核心逻辑需要注意两点:

  1. CancelToken怎么和config.cancelToken传递信息
  2. http请求内部,abort函数怎么和CancelToken交互

因此这个部分需要做好和两边数据传递。
目前的情况是有三个函数:cancel、executor、abort,我希望cancel调用之后立即调用abort,可以按照下面的写法。在xhr函数中把真正能够取消请求的abort函数传递给CancelToken存起来,然后CancelToken初始化的时候把包装abort函数的cancel函数作为参数传递给外部的函数。

function CancelToken(executor){
    this.real_cancel = null
    var token = this;
    executor(function cancel(message) {
        if (token.reason) {
            return;
        }
        token.reason = new Cancel(message);
        token.real_cancel && token.real_cancel()
    });
}

//xhr函数中
function xhr(config){
//...
 if (config.cancelToken) {
      config.cancelToken.real_cancel  = function(){
        if (!request) {
            return;
        }
        request.abort();
        request = null;
      }
}
//...
}

这样做有一个缺陷,取消请求是异步进行,如果我想在取消请求之后立即做某件事是做不到的。所以axios是用promise来组织这三者之间的顺序。

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

//xhr 函数中
    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        // reject(cancel);
        // Clean up request
        request = null;
      });
    }

首先把cancel函数传递给外部函数,只有cancel函数被调用了,CancelToken的promise才resolve,从而执行then里面的方法。在xhr函数中将取消请求的函数早就放到then里面去了,只是当时不会执行,等到cancel调用之后才执行。

四、对于取消请求的方法,这个部分我仅仅看懂了,但是还不能将源码中这种技巧灵活运用,所以这个部分用语言描述起来对我来说非常困难。

你可能感兴趣的:(【axios源码】请求取消的实现)