предисловие
При работе над проектом Vue я столкнулся с проблемой частого переключения меток. Из-за разного времени, необходимого для результатов ajax, запрашиваемых разными тегами, при нажатии разных тегов данные с самым медленным временем отклика перезаписывают данные, полученные ранее, а отображаемые данные не соответствуют выбранному тегу. В то время, чтобы справиться с этой проблемой, я не придумал хорошего способа, поэтому мне приходилось контролировать, прежде чем нажать на следующую вкладку, и мне приходилось ждать, пока вернется результат предыдущей вкладки.
Позже, когда я занимался унифицированным управлением API, я увидел, что в перехватчике аксиом, написанном моими предшественниками, есть CancelToken, я проверил информацию и обнаружил, что это может отменить запрос, и мне некуда идти. просто использовать для решения предыдущих встреч.Проблема частого переключения вкладок. Теперь сделайте запись, а лучше разберитесь с этой функцией.
Стремиться
При нажатии на вкладку ранее выполнявшийся запрос отменяется, поэтому при переключении вкладок страница получает результат последнего запроса вместо самого медленного ответа.
Применение
Официальный случай
- Создайте токен отмены, используя фабричный метод CancelToken.source, например:
// CancelToken是一个构造函数,用于创建一个cancelToken实例对象 // cancelToken实例对象包含了一个promise属性,值为可以触发取消请求的一个promise const CancelToken = axios.CancelToken; // 执行source()得到的是一个包含了cancelToken对象和一个取消函数cancel()的对象 // 即 source = {token: cancelToken对象, cancel: 取消函数} const source = CancelToken.source(); // 在请求的配置中配置cancelToken,那么这个请求就有了可以取消请求的功能 axios.get('/user/12345', { cancelToken: source.token }).catch(function(thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // 处理错误 } }); axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token }) // 执行取消请求(message 参数是可选的) source.cancel('Operation canceled by the user.');
- Создайте токен отмены, передав функцию-исполнитель конструктору CancelToken.
const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // executor 函数接收一个 cancel 函数作为参数 // 把cancel函数传递给外面,使得外面能控制执行取消请求 cancel = c; }) }); // cancel the request cancel();
Выглядит немного головокружительно, ведь я не знаю, как это в нем работает, а потом будет разбирать исходники. Простое объяснение здесь состоит в том, чтобы настроить в запросеcancelToken
Этот атрибут должен сделать запрос отменяемым;cancelToken
Стоимость имущества представляет собойCancelToken
объект экземпляра, в егоexecutor
извлечено из функцииcancel
функция, которая выполняет этоcancel
функция отмены запроса.
мой экземпляр
Нажмите на метку, чтобы выполнитьgetCourse
функция. При нажатии на вкладку сначала отмените предыдущий запрос (если предыдущий запрос был выполнен, отмена запроса ничего не даст). Эффект заключается в том, что на странице всегда отображается результат, соответствующий последнему нажатому тегу.
В два шага, первый шаг заключается в настройке в запросе на получениеcancelToken
атрибут, включите функцию отмены запроса, а в его атрибуте значение будетcancel
назначение функцийcancelRequest
, так что внешний может вызватьcancel
функция для отмены запроса; второй шаг — отменить предыдущий запрос перед выполнением запроса.
import axios from 'axios'
export default {
data() {
return {
cancelRequest: null // 初始时没有请求需要取消,设为null
}
},
methods: {
// 点击标签后发送请求的函数
getCourse() {
const that = this
// 2. 准备执行新的请求前,先将前一个请求取消
// 如果前一个请求执行完了,执行取消请求不会有其他操作
if (typeof that.cancelRequest === 'function') {
that.cancelRequest()
}
// 这里配置请求的参数,略
let params = {}
// 发送请求
axios.get('/api/app/course',{
params: params,
cancelToken: new CancelToken(function executor(c) {
// 1. cancel函数赋值给cancelRequest属性
// 从而可以通过cancelRequest执行取消请求的操作
that.cancelRequest = c
})
})
}
}
}
API, как правило, будет иметь единый пакет, чтобы запрос можно было инкапсулировать.
API
// /api/modules/course.js
// _this为vue组件实例对象
export function getCourseReq(params, _this) {
return axios.get('/api/app/course',{
params: params,
cancelToken: new CancelToken(function executor(c) {
// 1. cancel函数赋值给cancelRequest属性
// 从而可以通过cancelRequest执行取消请求的操作
_this.cancelRequest = c
})
})
.then(res => {})
.catch(err => {})
}
компоненты
import { getCourseReq } from '@/apis/modules/course'
methods: {
getCourse() {
// 2. 准备执行新的请求前,先将前一个请求取消
// 如果前一个请求执行完了,执行取消请求不会有其他操作
if (typeof this.cancelRequest === 'function') {
this.cancelRequest()
}
// 这里配置请求的参数,略
let params = {}
// 发送请求
getCourseReq(params, this)
.then(res => {})
.catch(err => {})
}
}
яма столкнулась
В начале было написано по вышеуказанной методике, но запрос на жизнь и смерть никто не отменял. Я снова и снова проверял имена переменных и отлаживал вывод информации, все было правильно, но я не мог отменить это. После долгого метания вспомнил аксиос настроенный предшественникамиinterceptor
настраивается для каждого запросаcancelToken
, цель состоит в том, чтобы удалить повторяющиеся запросы, но сегмент кода, который отменяет повторяющиеся запросы, закомментирован. ПучокcancelToken
После комментариев, наконец, стихают.
axios.interceptors.request.use(
config => {
const request =
JSON.stringify(config.url) +
JSON.stringify(config.method) +
JSON.stringify(config.data || '')
// 这里配置了cancelToken属性,覆盖了原请求中的cancelToken
config.cancelToken = new CancelToken(cancel => {
sources[request] = cancel
})
// if (requestList.includes(request)) {
// sources[request]('取消重复请求')
// } else {
requestList.push(request)
return config
},
error => {
return Promise.reject(error)
}
)
Поскольку Interceptor выполнит некоторую обработку конфигурации перед отправкой запроса, вот в исходном запросеcancelToken
перезаписывается, то даже если исходный запрос выполняетсяcancelToken
изcancel
функцию, потому чтоcancelToken
Объект другой, и операция отмены недействительна. Глядя на исходный код позже, вы можете понять, почему он недействителен.
Анализ исходного кода
Согласно предыдущим шагам, давайте рассмотрим каждый исходный код по очереди.
Сначала настраиваем запрос наcancelToken
Атрибут, цель которого сделать запрос иметь функцию отмены запроса, его значениеCancelToken
объект экземпляра, затемCancelToken
Что тогда?
// axios/lib/cancel/CancelToken.js
'use strict';
var Cancel = require('./Cancel');
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
/**
* 定义一个将来能执行取消请求的promise对象,当这个promise的状态为完成时(fullfilled),
* 就会触发取消请求的操作(执行then函数)。而执行resolve就能将promise的状态置为完成状态。
* 这里把resolve赋值给resolvePromise,就是为了在这个promise外能执行resolve而改变这个promise的状态
* 注意这个promise对象被赋值给CancelToken实例的属性promise,将来定义then函数就是通过这个属性得到promise
*/
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
/**
* 将CancelToken实例赋值给token
* 执行executor函数,将cancel方法传入executor,
* cancel方法可调用resolvePromise方法,即触发取消请求的操作
*/
var token = this;
executor(function cancel(message) {
if (token.reason) {
// 取消已响应 返回
return;
}
token.reason = new Cancel(message);
// 这里执行的就是promise的resolve方法,改变状态
resolvePromise(token.reason);
});
}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
// 这里可以看清楚source函数的真面目
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
// c 就是CancelToken中给executor传入的cancel方法
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
CancelToken
CancelToken
это конструктор, который проходитnew CancelToken()
Вы получаете объект-экземпляр, который имеет только одно свойствоpromise
, значением которого является объект Promise, запускающий запрос на отмену.
token = new CancelToken(executor function) ===> { promise: Promise对象 }
воплощать в жизньCancelToken
Функция делает две вещи:
- Создавать
Promise
объект и назначьте этот объектpromise
свойства, которыеresolve
Параметры выставлены для отмены выполнения функции. - воплощать в жизнь
executor
функция, которая передает внутреннюю функцию отмены в качестве параметраexecutor
// 源码相当于: var token = this; var cancel = function (message) { if (token.reason) { // 取消动作已响应 返回 return; } token.reason = new Cancel(message); // 这里执行的就是promise的resolve方法,改变状态 resolvePromise(token.reason); } executor(cancel);
Так что исполнение
let cancel
token = new CancelToken(function executor(c) {
cancel = c;
});
Результат:
-
token
значение{promise: Promise对象}
-
executor
функция выполняется, т.е.cancel = c
выполнить, поэтому переменнаяcancel
присваивается, значениеCanelToken
тот внутриcancel
функция.
Внешний вид, найти Promise на самом деле является входящим EXECUTOR, то же самое, что и выполнение нового обещания (Executor)
CancelToken.source
CancelToken.source
Это функция, которую можно увидеть в исходном коде и выполнитьconst source = CancelToken.source()
, результатом является объект:
return {
token: token,
cancel: cancel
};
содержитtoken
объект, то естьCancelToken
объект экземпляра иcancel
функция. следовательноCancelToken.source()
Функция функции состоит в том, чтобы генерироватьtoken
возразить и получитьcancel
функция.token
объект используется для настройкиaxios
в запросеcancelToken
свойство, которое содержит объект обещания,cancel
Функция — это функция, которая будет инициировать запросы на отмену в будущем.
Связь между токеном и отменой заключается в том, что промис, который отменяет запрос, хранится в токене, а функция разрешения, которая изменяет состояние промиса, хранится в отмене, Один из них является исполнителем, а другой — переключателем. При срабатывании переключателя изменяется состояние исполнительного органа и выполняется соответствующая ему функция. Они связаны через свойство состояния промисов.
Как выполнить запрос на отмену
Теперь, когда все готово, осталось только выполнить запрос на отмену. Это то, что меня больше всего озадачило в начале, давайте посмотрим. Вот исходный код для запуска запроса:
// axios/lib/adapters/xhr.js
// 创建XHR对象
var request = new XMLHttpRequest()
// 模拟当前ajax请求
request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true)
// 定义取消请求promise对象的then方法
if (config.cancelToken) { // 如果配置了cancelToken属性
// 当promise为完成态时,这个then函数执行,即执行取消请求
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 取消ajax请求
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
Здесь, наконец, выполняется запрос ajax.
Мы настроили ранееcancelToken = new CancelToken(executor)
, получить этот объект
cancelToken = {promise: 取消请求的Promise对象}
Поэтому вышеизложенноеcancelToken.promise
Это оноpromise
.
мы знаем,Promise
изthen
функция находится вPromise
объект находится вfullfilled
При выполнении это неявная ассоциация. воплощать в жизньcancel
функция, будетPromise
статус установлен наfullfilled
, определено здесьthen
Функция выполняется, отменяя запрос.
Проблема с перехватчиком
Проблемы, встречавшиеся ранее, настроенные в перехватчикеcancelToken
перезаписывает запросcancelToken
, что приводит к сбою запроса. Причина в том, что перехватчик переопределен,cancelToken
Значение атрибута становится новым токеном,cancelToken.promise
становится новымpromise
.then
Функция определяется после перехватчика и до фактической отправки запроса, поэтому она определена на данный момент.then
Функция соответствует этому новому объекту Promise, поэтому обещание исходного токена не имеет функции then, тогда выполняется исходное обещание.cancel
Функция устанавливает исходный объект Promise вfullfilled
Состояние, но тогда соответствующая функция не выполняется, было сообщено, нет выполнения, отмена запроса недействительна.
процесс axios: определить запрос -> перехватчик запроса -> отправить запрос -> перехватчик ответа -> получить ответ
послесловие
Как только вы введете исходный код, он станет глубоким, как море. Прочитав его, вы получите много пользы. Некоторые из идей и практик здесь можно почерпнуть.