【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调用之后才执行。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容