重复请求,前面的覆盖后面的结果(最佳实践)

本文详细解析了axios中请求取消的实现方式,包括通过CancelToken.source()和构造函数CancelToken来取消请求,并展示了如何在实际场景中应用,如处理多个并发请求。此外,还介绍了封装方法以管理和取消多个请求,确保请求管理的有序性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请求取消,往往使用在当网络较慢,用户在等待当前请求时转而去点了其他请求,且这两个请求是会相互影响时,或者关闭当前标签页,我们会将当前请求取消。例如远程模糊搜索、搜索建议、路由切换。axios提供了请求取消的接口,在github上官方给出了两种实现请求取消的用法

简单处理
  • 方法一
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.');

解析

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();
我们取第一个例子来解析每一步,首先第一步,将axios中的CancelToken赋值给一个变量CancelToken,我们看看axios中的CancelToken

function CancelToken(executor) {
  // 检测传入的是否为函数
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  // 使用promise异步
  // 在后面调用方法时就会触发promise的resolve
  // 在xhr.js中调用abort方法取消请求
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // 已经发起过取消请求的情况下,会直接return
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}
/**
 * 当请求已经被取消时,抛出Cancel对象
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

/**
 * 返回一个包含新的CancelToken的对象,以及一个用来取消请求的方法
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // 这里的token实际上就是新的CancelToken对象
  // cancel是用来取消请求的方法
  return {
    token: token,
    cancel: cancel
  };
};


这里还不太清楚内部的结构没关系,我们先往下走,这里先知道我们创建了一个CancelToken即可。
看看下一行代码

const source = CancelToken.source();
这里调用了CancelToken的source方法,将返回的值赋值给source,从代码中的source方法

/**
 * 返回一个包含新的CancelToken的对象,以及一个用来取消请求的方法
 */
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // 这里的token实际上就是新的CancelToken对象
  // cancel是用来取消请求的方法
  return {
    token: token,
    cancel: cancel
  };
};

我们可以明确看到,变量source是一个包含新的CancelToken和取消对应token的方法的对象。将executor作为new CancelToken的参数,当我们执行这里的方法c,即返回对象中的cancel时,就会触发下面一系列操作

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

进入CancelToken后,首先判断传入的是不是一个函数,不是的话会抛出错误,这里是一个函数,继续往下走,新建了一个Promise赋值给这个CancelToken对象的promise属性,将resolve赋值给resolvePromise使得我们可以在外部控制promise的状态

var resolvePromise;
// 使用promise异步
// 在后面调用方法时就会触发promise的resolve
// 在xhr.js中调用abort方法取消请求
this.promise = new Promise(function promiseExecutor(resolve) {
  resolvePromise = resolve;
});

在这里,只要我们调用了resolvePromise方法,就等于将this.promise的状态变为resolve,接下来将this赋值给token,将一个cancel方法作为参数传给刚刚传给new CancelToken的参数的方法

var token = this;
executor(function cancel(message) {
  // 已经发起过取消请求的情况下,会直接return
  // 在下一步我们就将请求时会出项错误的信息赋值给token.reason,说明已经发起过这个请求了
  if (token.reason) {
    return;
  }

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

当token.reason存在时,即已经执行过这个取消请求的操作了,如果执行过,直接return,如果没执行过,我们将Cancel对象作为token.reason的值,然后将其作为参数执行resolvePromise方法,将promise变为resolve的状态。

Cancel对象实际上就是在请求被取消时会抛出来的错误信息,看看源代码

'use strict';
/**
 * A `Cancel` is an object that is thrown when an operation is canceled.
 *
 * @class
 * @param {string=} message The message.
 */
// 当请求已经被取消的时候再取消会抛出这个Cancel对象作为错误信息
function Cancel(message) {
  this.message = message;
}

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

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

知道这些后,接下来通过一个请求,我们来将请求时取消请求的具体流程弄清楚

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

在这段代码中,我们使用axios发起了一个get请求,然后将source.token即上面我们调用source方法创建的对象中新建的那个CancelToken传入当前请求的cancelToken属性中,这里我们看看xhr.js中的相关内容

if (config.cancelToken) {
  // 对取消请求的操作
  // 当promise变为resolve时,执行then里的方法
  config.cancelToken.promise.then(function onCanceled(cancel) {
    // 如果请求已经没了,即请求已经结束
    // 那么直接返回
    if (!request) {
      return;
    }
    // 取消请求
    request.abort();
    reject(cancel);
    // 清除请求
    request = null;
  });
}

  • 方法二
通过axios.CancelToken构造函数生成取消函数
const CancelToken = axios.CancelToken;
let cancel;
​
axios.get('/user/12345', {
 cancelToken: new CancelToken(function executor(c) {
 // 执行函数接收取消函数作为参数
 cancel = c;
  })
});
​
// cancel the request
cancel();
封装,可以同时处理多个请求
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pending = new Map()
/**
 * 添加请求
 * @param {Object} config 
 */
const addPending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
    }
  })
}
/**
 * 移除请求
 * @param {Object} config 
 */
const removePending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}
/**
 * 清空 pending 中的请求(在路由跳转时调用)
 */
export const clearPending = () => {
  for (const [url, cancel] of pending) {
    cancel(url)
  }
  pending.clear()
}


// 主要的方法已经写好了,只需要添加到axios拦截器中就可以了。
axios.interceptors.request.use(config => {
  removePending(options) // 在请求开始前,对之前的请求做检查取消操作
  addPending(options) // 将当前请求添加到 pending 中
  // other code before request
  return config
}, error => {
  return Promise.reject(error)
})
​
axios.interceptors.response.use(response => {
  removePending(response) // 在请求结束后,移除本次请求
  return response
}, error => {
  if (axios.isCancel(error)) {
    console.log('repeated request: ' + error.message)
  } else {
    // handle error code
  }
  return Promise.reject(error)
})
// 将clearPending()方法添加到vue路由钩子函数中
router.beforeEach((to, from, next) => {
  clearPending()
  // ...
  next()
})

注:qs是一个专门用来转换对象和字符串参数的库,最初是由 TJ 创建并维护的,也是axios推荐使用的参数序列化库。这里我们的目的只是单纯的将参数对象转换为字符串方便拼接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值