Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

axios:取消请求的使用案例分析 #3

Open
ohhoney1 opened this issue Sep 15, 2018 · 6 comments
Open

axios:取消请求的使用案例分析 #3

ohhoney1 opened this issue Sep 15, 2018 · 6 comments
Labels
summary Summary notes about one problem

Comments

@ohhoney1
Copy link
Owner

document

https://github.com/axios/axios#cancellation

取消http请求,axios文档里提供了两种用法:

// 第一种:使用 CancelToken
const { CancelToken, isCanCel } = axios;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(thrown => {
  if (isCancel(thrown)) {
  	// 获取 取消请求 的相关信息
    console.log('Request canceled', thrown.message);
  } else {
    // 处理其他异常
  }
});

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

// 取消请求。参数是可选的,参数传递一个取消请求的相关信息,在 catch 钩子函数里能获取到
source.cancel('Operation canceled by the user.');

// 第二种:给构造函数 CancelToken 传递一个 executor 函数作为参数。这种方法的好处是,可以用同一个 cancel token 来取消多个请求
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // 参数 c 也是个函数
    cancel = c;
  })
});

// 取消请求,参数用法同上
cancel();

项目中用法示例

在一个真实的项目中,一般都会对axios进行二次封装,针对请求、响应、状态码、code等做处理。贴一个项目里常用的request.js:

import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 创建一个 axios 实例,并改变默认配置
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // request timeout
})

// 请求拦截
service.interceptors.request.use(
  config => {
    // Do something before request is sent
    if (store.getters.token) {
      // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// 响应拦截
service.interceptors.response.use(
  response => response,
  error => {
    alert(error)
    return Promise.reject(error)
  }
)

export default service

对于某一个请求添加取消的功能,要在调用api时,加上cancelToken选项,使用时的示例:

// api.js
import request from 'request'
export function getUsers(page, options) {
  return request({
    url: 'api/users',
    params: {
      page
    },
    ...options
  })
}


// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: null

...

toCancel() {
  this.cancel('取消请求')
}

getUsers(1,
  {
    cancelToken:  new CancelToken(c => (this.cancel = c))
  }
)
.then(...)
.catch(err => {
  if (isCancel) {
    console.log(err.message)
  } else {
    ...
  }
})

以上,我们就可以顺顺利利地使用封装过的axios,取消某一个请求了。其原理无非就是把cancelToken的配置项,在调用api时加上,然后就可以在业务代码取消特定请求了。

批量取消请求

document 里的第二种方法已经说过:通过指定同一个cancel token来取消。但是,在上面的项目示例中,不能控制拿到相同的cancel token。我们可以换个思路:用数组保存每个需要取消的cancel token,然后逐一调用数组里的每一项即可:

// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: []

...

toCancel() {
  while (this.cancel.length > 0) {
    this.cancel.pop()('取消请求')
  }
}

getUser1(1,
  {
    cancelToken:  new CancelToken(c1 => (this.cancel.push(c1)))
  }
)

getUser2(2,
 {
  cancelTokem: new CancleTokem(c2 => (this.cancel.push(c2)))
 }
)

切换路由时,取消请求

上面讲了取消一个请求及页面内批量abort的方法,此外,还有一种需求——切换路由时,取消所有。

这里不详细赘述了,大概思路就是在请求拦截器里,统一加个token,并设置全局变量source控制一个cancel token,在路由变化时调用cancel方法。具体实现,见 参考链接4

取消请求的实现原理

cancelTokensource方法维护了一个对象,里面包括了token令牌和cancel方法,token来自与构造函数CancelToken,调用cancel方法后,token的promise状态为resolved,进而又调用了xhr的abort方法,取消请求成功。具体见 参考链接5

参考链接

  1. axios
  2. vue-element-admin
  3. axios cancelToken 如何在切换路由的时候来取消所有请求
  4. 路由变化时使用axios取消所有请求
  5. axios源码分析——取消请求
@ohhoney1 ohhoney1 changed the title axios:取消请求 axios:取消请求的使用案例分析 Sep 15, 2018
@ohhoney1 ohhoney1 added the summary Summary notes about one problem label Sep 15, 2018
@ohhoney1
Copy link
Owner Author

data里边cancel 定义了 null ,后边还用 this.cancel('')?

当时的业务逻辑应该是在页面加载时,或者说组件创建时就去请求,请求中先赋值了:new CancelToken(c => (this.cancel = c)),调用 toCancelthis.cancel已经有值了。
为了保险起见,this.cancel && this.cancel() 更规范。

@yanye411325
Copy link

yanye411325 commented Apr 14, 2020 via email

@ohhoney1
Copy link
Owner Author

@yanye411325 不太理解你说的,或许你可以给个复现场景

@debug-null
Copy link

很棒,厉害,感谢大佬

@demon-zhonglin
Copy link

如何做到全局控制取消请求和单条取消请求两者都存在呢?
就如代码中
getUsers(1, { cancelToken: new CancelToken(c => (this.cancel = c)) } ) .then(...) .catch()
此时可通过 toCancel 方法取消,然后也需要 切换路由时取消 getUsers 的请求。

根据 参考链接4 中,在请求拦截器中 写入的全局 cancelToken 已经覆盖了单条请求传入的 cancelToken 了,做不到单条控制了

@demon-zhonglin
Copy link

demon-zhonglin commented Nov 13, 2020

如何做到全局控制取消请求和单条取消请求两者都存在呢?
就如代码中
getUsers(1, { cancelToken: new CancelToken(c => (this.cancel = c)) } ) .then(...) .catch()
此时可通过 toCancel 方法取消,然后也需要 切换路由时取消 getUsers 的请求。

根据 参考链接4 中,在请求拦截器中 写入的全局 cancelToken 已经覆盖了单条请求传入的 cancelToken 了,做不到单条控制了

自己解决了,基于上述代码做了些许修改
(下方代码引用了第三方库: lodash)

// 全局变量(存储)
window.globalRequestStorage = [
  {
    token: null,
    cancel: null
  }
]

// 请求拦截器
service.interceptors.request.use(config => {
    if (_.has(config, 'cancelFunction') && _.has(config, 'cancelFunction')) { // cancelFunction: (单条请求调用时传入的自定义参数)
      window.globalRequestStorage.push({
        token: null,
        cancel: config.cancelFunction
      })
      delete config.cancelFunction
    } else {
      const newCancelToken = CancelToken.source()
      config.cancelToken = newCancelToken.token // 请求取消所需标识
      window.globalRequestStorage.push(newCancelToken)
    }

    return config
  },
  error => {...}
)


router.beforeEach(async(to, from, next) => {
  if (_.isArray(window.globalRequestStorage)) {
    window.globalRequestStorage.forEach(source => {
      source.cancel && source.cancel('no show')
    })
    window.globalRequestStorage = []
  }

  next()
})

// 调用
let cancelFunction = null
getUser1({
  cancelToken: new CancelToken(cancel => {
    this.requestCancel = cancel
    cancelFunction = cancel
  }),
  cancelFunction: cancelFunction // 传入New CancelToken返回的 cancel 方法(自定义参数)
})


// 取消请求
this.requestCancel && this.requestCancel()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
summary Summary notes about one problem
Projects
None yet
Development

No branches or pull requests

4 participants