关于javascript:axios取消功能详解

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 {
    // handle error
  }
});

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

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

第二种形式如下:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

// cancel the request
cancel();

官网实现勾销性能的文件寄存在 /lib/cancel/CancelToken.js

尽管代码不多,然而第一次看真是一头雾水,上面就来抽丝剥茧,一步步还原外面的实现逻辑。

剖析

两种形式都调用了 CancekToken 这个构造函数,咱们就先从这个构造函数开始。

剖析:

第一种形式:

  • CancekToken 提供一个静态方法sourcesource办法返回tokencancel办法

第二种形式:

  • CancekToken 接管一个回调函数作为参数,回调函数接管cancel勾销办法

第二种形式更容易动手,咱们能够先实现构造函数CancekToken,再思考第一种形式静态方法source的实现。

简易版 axios

首先咱们写个简易版的axios,不便咱们前面的剖析和调试:

知识点:Promise、XMLHttpRequest

function axios(url,config){
  return new Promise((resolve,reject)=>{
    const xhr = new XMLHttpRequest();
    xhr.open(config.method || "GET",url);
    xhr.responseType = config.responseType || "json";
    xhr.onload = ()=>{
      if(xhr.readyState === 4 && xhr.status === 200){
        resolve(xhr.response);
      }else{
        reject(xhr)
      }
    };
    xhr.send(config.data ? JSON.stringify(config.data) : null);
  })
}

CancelToken

第二种形式中,咱们能够看到 CancelToken 在配置参数cancelToken中实例化:

axios.get('/user/12345', {
  cancelToken: new CancelToken
});

所以在axios中,咱们也会依据配置中是否蕴含cancelToken来勾销发送:

function axios(url,config){
  return new Promise((resolve,reject)=>{
    const xhr = new XMLHttpRequest();
    ...
    if(config.cancelToken){
      // 如果存在 cancelToken 参数
      // xhr.abort() 终止发送工作
      // reject() 走reject办法
    }
    ...

回到配置参数,CancelToken承受一个回调函数作为参数,参数蕴含勾销的cancel办法,咱们初始化CancelToken办法如下:

function CancelToken(executor){
  let cancel = ()=>{};
  executor(cancel)
}

回到官网例子,例子中参数cancel办法被赋值给以后环境的cancel变量,于是以后环境cancel变量指向CancelToken办法中的cancel函数表达式。

let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c; // 指向CancelToken中的 cancel 办法
  })
});

接下来cancel办法一旦被执行,就能触发申请终止。

听起来是不是很相熟!

就是公布订阅嘛!

这里官网源码奇妙的应用了Promise链式调用的形式实现,咱们给CancelToken办法返回一个Promise办法:

function CancekToken(executor){
  let cancel = ()=>{};
  const promise = new Promise(resolve => cancel = resolve);
  executor(cancel);
  return promise;
}

接下来只有用户执行cancel办法,配置参数cancelToken取得的Promise办法就能响应了:

let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c; // 指向CancelToken中的 cancel 办法
  })
});
// 执行
+ cancel("canceled request");

axios中响应cancel(Promise)办法:

function axios(url,config){
  return new Promise((resolve,reject)=>{
    const xhr = new XMLHttpRequest();
    ...
    if(config.cancelToken){
+       config.cancelToken.then(reason=>{
+        xhr.abort();
+        reject(reason);
      })
    }
    ...

关键点是把 Promise.resolve 从函数外部抽出来,奇妙的实现了异步拆散

到了这里,第二种办法的勾销性能就根本实现了。

CancekToken.source

source作为 CancekToken 提供的静态方法,返回tokencancel 办法。

cancel办法跟后面的性能是一样的,能够了解成部分环境外面申明好cancel再抛出来。

咱们再来看看第二种形式 token 在配置中的应用:

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

axios.get('/user/12345', {
  cancelToken: source.token // token 返回的是CancelToken实例
})

依据后面的配置咱们能够晓得 source.token 实际上返回的是 CancelToken 实例。

理解 source 办法须要返回的对象性能后,就能够轻松实现source办法了:

CancekToken.source = function(){

  let cancel = ()=>{};
  const token = new CancekToken(c=>cancel = c);

  return {
    token,
    cancel
  }
}

axios.isCancel

通过上的代码咱们晓得,勾销申请会走reject办法,在Promise中能够被catch到,不过咱们还须要判断catch的谬误是否来自勾销办法,这里官网提供了isCancel办法判断:

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (error) {
  // 判断是否 勾销操作
  if (axios.isCancel(error)) {}
});

在js中咱们能够通过instanceof判断是否来自某个构造函数的实例,这里新建Cancel办法来治理勾销发送的信息:

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

CancekToken.source 返回的cancel办法通过函数包装,实例化一个Cancel作为勾销参数:

CancekToken.source = function(){
  
-  let cancel = ()=>{};
-  const token = new CancekToken(c=>cancel = c);
  
+ let resolve = ()=>{};
+  let token = new CancekToken(c=>resolve = c);

  return {
    token,
-    cancel,
+   cancel:(reason)=>{
+     // 实例化一个小 cancel,将 reason 传入
+     resolve(new Cancel(reason))
+   }
  }
}

最终Promise.catch到的参数来自实例Cancel,就能够很容易的判断error是否来自Cancel了:

function isCancel(error){
  return error instanceof Cancel
}
// 将 `isCancel` 绑定到 axios
axios.isCancel = isCancel

最初,官网还判断了CancelToken.prototype.throwIfRequested,如果调用了cancel办法,具备雷同cancelToken配置的ajax申请也不会被发送,这里能够参考官网代码的实现。

全副代码

最初是全副代码实现:

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

function CancekToken(executor) {
  let reason = null
  let resolve = null
  const cancel = message => {
    if(reason) return;
    reason = new Cancel(message);
    resolve(reason)
  }
  const promise = new Promise(r => (resolve = r))
  executor(cancel)
  return promise
}

CancekToken.source = function() {
  let cancel = () => {}
  let token = new CancekToken(c => (cancel = c))

  return {
    token,
    cancel
  }
}

const source = CancekToken.source()

axios('/simple/get', {
  cancelToken: source.token
}).catch(error => {
  if (axios.isCancel(error)) {
    console.log(error)
  }
})

source.cancel('canceled http request 1')

let cancel
axios('/simple/get', {
  cancelToken: new CancekToken(c => {
    cancel = c
  })
}).catch(error => {
  if (axios.isCancel(error)) {
    console.log(error)
  }
})
cancel('canceled http request 2')

function axios(url, config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open(config.method || 'GET', url)
    xhr.responseType = config.responseType || 'json'

    if (config.cancelToken) {
      config.cancelToken.then(reason => {
        xhr.abort()
        reject(reason)
      })
    }

    xhr.onload = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(xhr)
      }
    }
    xhr.send(config.data ? JSON.stringify(config.data) : null)
  })
}

axios.isCancel = function(error) {
  return error instanceof Cancel
}

es6繁难版本的实现:

export class Cancel {
    public reason:string;
    constructor(reason:string){
        this.reason = reason
    }
}
export function isCancel(error:any){
    return error instanceof Cancel;
}
export class CancelToken {
    public resolve:any;
    source(){
        return {
            token:new Promise(resolve=>{
                this.resolve = resolve;
            }),
            cancel:(reason:string)=>{
                this.resolve(new Cancel(reason).reason)
            }
        }
    }
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理