axios取消请求cancelToken和AbortController

目录

本质:观察订阅+xhr.abort()

就绪状态readyState:0

状态码status:0

cancelToken

源码

CancelToken

(un)subscribe

 source

示例

A.axios.canceltoken

B.source对象创建canceltoken

AbortController接口:(控制器对象)

源码

替换原因

保持与fetch一样的调用方式,让开发者更好上手

CancelToken存在内存泄露隐患【切片上传长时间没有中断】

示例

axios

fetch

本质:观察订阅+xhr.abort()

就绪状态readyState:0

  • 取值范围是 0 到 4,分别表示不同的状态:
    • 0: 请求未初始化(open 方法还未调用)。
    • 1: 服务器连接已建立(open 方法已经调用)。
    • 2: 请求已接收(send 方法已经调用,并且头部和状态已经可获得)。
    • 3: 请求处理中(响应体正在接收)。
    • 4: 请求已完成,且响应已就绪。

状态码status:0

// 创建AbortController实例且存放到controller上
// 注意这里每次请求都会创建一个新的AbortController实例,
//是因为AbortController实例调用abort后
//AbortController实例的状态signal就为aborted不能更改
    controller.current = new AbortController();
    const xhr = new XMLHttpRequest();
    xhr.open("get", "https://mdn.github.io/dom-examples/abort-api/sintel.mp4");
    xhr.onreadystatechange = () => {
      if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        setMessage("下载成功");
        setLoading(false);
      }
    };
    // 监听AbortController实例的abort事件,当AbortController实例调用abort方法时,就会触发该事件执行回调
    controller.current.signal.addEventListener("abort", () => {
      setMessage("下载中止");
      setLoading(false);
      xhr.abort();
    });
    xhr.send();
// 调用AbortController实例的abort方法,从而触发上面注册在abort事件的回调的执行
  const abortDownload = () => {
    controller.current.abort();
  };

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) {
    resolvePromise = resolve;
  });

  var token = this;

  this.promise.then(function (cancel) {
    if (!token._listeners) return;

    var i;
    var l = token._listeners.length;
// 遍历执行所有注册的监听器
    for (i = 0; i < l; i++) {
      token._listeners[i](cancel);
    }
// 清空监听器数组
    token._listeners = null;
  });

  // source.cancel指向此处的cancel函数
//在取消时调用 resolvePromise 函数
  executor(function cancel(message) {
    // token.reason有值代表cancel已被执行,CancelToken是一个一次性的Subject,notify一次后即失效
    if (token.reason) {
      return;
    }

    token.reason = new CanceledError(message);
    // resolvePromise执行时,会执行上面this.promise.then中传入的回调函数。从而把listeners全执行
    resolvePromise(token.reason);
  });
}
(un)subscribe

  1. 请求前subscribe注册onCancel函数
  2. 请求后unsubscribe注销onCancel函数
CancelToken.prototype.subscribe = function subscribe(listener) {
  // 如果CancelToken实例已经执行cancel,直接执行该回调函数
  if (this.reason) {
    listener(this.reason);
    return;
  }

  // 如果CancelToken实例还没执行cancel,则把回调函数放进_listeners里
  if (this._listeners) {
    this._listeners.push(listener);
  } else {
    this._listeners = [listener];
  }
};

// 把回调函数从_listeners中移除
CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
  if (!this._listeners) {
    return;
  }
  var index = this._listeners.indexOf(listener);
  if (index !== -1) {
    this._listeners.splice(index, 1);
  }
};
 source
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel,
  };
};

示例

A.axios.canceltoken

//初始化
let cancel = null;   

//请求
if(cancel != null) cancel()     //如果上一次的请求还在继续,则取消

axios({
			method:"get",
			url:"http://localhost:3000/test.php",
			cancelToken:new axios.CancelToken(function(c){
				cancel = c
			})
		}).then(response=>{
			//处理响应数据
			cancel = null
		}).catch(reason=>{
			//错误处理
		})
}

B.source对象创建canceltoken

let source = axios.CancelToken.source();

       // 判断上一次的请求是否还在继续,如果还在继续,则取消上一次的请求
       if(source.token._listeners!=undefined )
        {
            source.cancel("取消请求")
            source = axios.CancelToken.source()
        }
        axios.get('http://localhost:3000/front-end/axios/response.php',{
            cancelToken:source.token
            }).then(response=>{
                // 处理响应
            }).catch(reason=>{
                if(axios.isCancel(reason)){
                    console.log("取消请求",reason)
                }else{ 
                    //错误处理
                }
            })
        }

AbortController接口:(控制器对象)

2021 年 10 月推出的AxiosV0.22.0版本中把CancelToken打上 👎deprecated 的标记,意味废弃。与此同时,推荐 AbortController 来取而代之

源码

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var onCanceled;
    // done函数用于在请求结束后注销回调函数,以免发生内存泄漏
    function done() {
      // 如果使用CancelToken实例,则会在下面发出请求逻辑之前通过subscribe注册onCanceled函数
      if (config.cancelToken) {
        config.cancelToken.unsubscribe(onCanceled);
      }
      // 如果使用AbortController实例,则会在下面发出请求逻辑之前通过signal.addEventListener监听abort事件且注册onCancel作为回调函数
      if (config.signal) {
        config.signal.removeEventListener("abort", onCanceled);
      }
    }

    var request = new XMLHttpRequest();

    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(
      config.method.toUpperCase(),
      buildURL(fullPath, config.params, config.paramsSerializer),
      true
    );

    function onloadend() {
      // 生成response对象
      var response = { data, status, statusText, headers, config, request };
      // settle函数内部根据response.status或config.validateStatus去调用_resolve或_reject
      settle(
        function _resolve(value) {
          resolve(value);
          done();
        },
        function _reject(err) {
          reject(err);
          done();
        },
        response
      );
    }

    if ("onloadend" in request) {
      // Use onloadend if available
      request.onloadend = onloadend;
    } else {
      // Listen for ready state to emulate onloadend
      request.onreadystatechange = function handleLoad() {
        if (!request || request.readyState !== 4) {
          return;
        }

        if (
          request.status === 0 &&
          !(request.responseURL && request.responseURL.indexOf("file:") === 0)
        ) {
          return;
        }

        // onreadystatechange事件会先于onerror或ontimeout事件触发
        // 因此onloaded需要在下一个事件循环中执行
        setTimeout(onloadend);
      };
    }

    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {};

    // Handle low level network errors
    request.onerror = function handleError() {};

    // Handle timeout
    request.ontimeout = function handleTimeout() {};

    // 处理用到CancelToken或AbortController的情况
    if (config.cancelToken || config.signal) {
      // 取消请求的函数
      onCanceled = function (cancel) {
        if (!request) {
          return;
        }
        reject(
          !cancel || (cancel && cancel.type) ? new CanceledError() : cancel
        );
        request.abort();
        request = null;
      };
      // 如果是用CancelToken取消请求,则把onCanceled注册到CancelToken实例上,
      // CancelToken实例本质上是一个观察者模式中的Subject
      config.cancelToken && config.cancelToken.subscribe(onCanceled);
      // 如果是用AbortController,则先从AbortController实例的signal.aborted判断其是否已调用abort,
      // 如果已调用,直接执行onCanceled,如果没有则直接在signal上监听其事件,逻辑和开头展示AbortController取消XHR请求的例子一样
      // axios.request在调用时,会return一条动态生成的promise链,链上的顺序是:
      //    Promise.resove(config)->所有请求拦截器(onFulfilled,onRejected)->(dispatchRequest,undefined)->所有响应拦截器(onFulfilled,onRejected)
      // dispatchRequest就是调用config.adapter或default.adapter去发出请求,
      // 因为存在执行请求拦截器途中,AbortController实例已调用aborted的情况,因此这里要对config.signal.aborted做判断处理
      if (config.signal) {
        config.signal.aborted
          ? onCanceled()
          : config.signal.addEventListener("abort", onCanceled);
      }
    }

    request.send(requestData);
  });
};

替换原因

  • 保持与fetch一样的调用方式,让开发者更好上手

    Axios官方一直保持自身的调用方式与fetch相似,如下所示:

  • fetch(url,config).then().catch()
    axios(url,config).then().catch()
    

    而目前fetch唯一中断请求的方式就是与AbortController搭配使用。Axios通过支持与fetch一样调用AbortController实现中断请求的方式,让开发者更方便地从fetch切换到Axios。目前就实用性而言,XHR还是比fetch要好,例如sentry在记录面包屑的接口信息方面,XHR请求可以比fetch请求记录更多的数据。还有目前fetch还不支持onprogress这类上传下载进度事件。

  • CancelToken存在内存泄露隐患【切片上传长时间没有中断】

    切片上传过程中没有发生中断或者很久才发生中断,则cancelToken.promise会一直存在在内存里,而由于xhrAdaptercancelToken.promise通过.then(function onCancel(){...})挂载了很多个onCancel

示例

axios

image.png

fetch

controller.current = new AbortController();
    fetch("https://mdn.github.io/dom-examples/abort-api/sintel.mp4", {
      // fetch配置中仅需把signal指向AbortController实例的signal即可
      signal: controller.current.signal,
    })
      .then(() => {
        setMessage("下载成功");
        setLoading(false);
      })
      .catch((e) => {
        setMessage("下载错误:" + e.message);
        setLoading(false);
      });
  };

  const abortDownload = () => {
    controller.current.abort();
  };
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值