闽公网安备 35020302035485号
那么如何解决竞态问题呢?在以上这些场景中,我们很容易想到:当发出新的请求时,取消掉上次请求即可。
// 堆代码 duidaima.com
const xhr= new XMLHttpRequest();
xhr.open('GET', 'https://xxx');
xhr.send();
xhr.abort(); // 取消请求
2.fetch API 取消请求const controller = new AbortController();
const signal = controller.signal;
fetch('/xxx', {
signal,
}).then(function(response) {
//...
});
controller.abort(); // 取消请求
3. axios 取消请求const source = axios.CancelToken.source();
axios.get('/xxx', {
cancelToken: source.token
}).then(function (response) {
// ...
});
source.cancel() // 取消请求
在 cancel 时,axios 会在内部调用 promise.reject() 与 xhr.abort()。
axios.get('/xxx', {
cancelToken: source.token
}).catch(function(err) {
if (axios.isCancel(err)) {
console.log('Request canceled', err.message);
} else {
// 处理错误
}
});
但 cancelToken 从 v0.22.0 开始已被 axios 弃用。原因是基于实现该 API 的提案 cancelable promises proposal[1] 已被撤销。从 v0.22.0 开始,axios 支持以 fetch API 方式的 AbortController 取消请求。const controller = new AbortController();
axios.get('/xxx', {
signal: controller.signal
}).then(function(response) {
//...
});
controller.abort() // 取消请求
同样,在处理请求错误时,也需要判断 error 是否来自 cancel。import { createImperativePromise } from 'awesome-imperative-promise';
const { resolve, reject, cancel } = createImperativePromise(promise);
resolve("some value");
// or
reject(new Error());
// or
cancel();
内部的 cancel 方法其实就是将 resolve,reject 设为 null,让 promise 永远不会 resolve/reject。
function onlyResolvesLast(fn) {
// 保存上一个请求的 cancel 方法
let cancelPrevious = null;
const wrappedFn = (...args) => {
// 当前请求执行前,先 cancel 上一个请求
cancelPrevious && cancelPrevious();
// 执行当前请求
const result = fn.apply(this, args);
// 创建指令式的 promise,暴露 cancel 方法并保存
const { promise, cancel } = createImperativePromise(result);
cancelPrevious = cancel;
return promise;
};
return wrappedFn;
}
以上就是 awesome-only-resolves-last-promise的实现。只需要将 onlyResolvesLast 包装一下请求方法,就能实现自动忽略,减少很多模板代码。const fn = (duration) =>
new Promise(r => {
setTimeout(r, duration);
});
const wrappedFn = onlyResolvesLast(fn);
wrappedFn(500).then(() => console.log(1));
wrappedFn(1000).then(() => console.log(2));
wrappedFn(100).then(() => console.log(3));
// 输出 3
let fetchId = 0; // 保存最新的请求 id
const getUsers = () => {
// 发起请求前,生成新的 id 并保存
const id = fetchId + 1;
fetchId = id;
await 请求
// 判断是最新的请求 id 再处理回调
if (id === fetchId) {
// 请求处理
}
}
上面的使用方法也会在项目中产生很多模板代码,经过封装后,也能实现一套同样用法的 onlyResolvesLast:function onlyResolvesLast(fn) {
// 利用闭包保存最新的请求 id
let id = 0;
const wrappedFn = (...args) => {
// 发起请求前,生成新的 id 并保存
const fetchId = id + 1;
id = fetchId;
// 执行请求
const result = fn.apply(this, args);
return new Promise((resolve, reject) => {
// result 可能不是 promise,需要包装成 promise
Promise.resolve(result).then((value) => {
// 只处理最新一次请求
if (fetchId === id) {
resolve(value);
}
}, (error) => {
// 只处理最新一次请求
if (fetchId === id) {
reject(error);
}
});
})
};
return wrappedFn;
}
用法也一样,使用 onlyResolvesLast 包装一下请求方法,实现过期请求自动忽略。而且,这样的实现不依赖指令式 promise,也更轻量。const fn = (duration) =>
new Promise(r => {
setTimeout(r, duration);
});
const wrappedFn = onlyResolvesLast(fn);
wrappedFn(500).then(() => console.log(1));
wrappedFn(1000).then(() => console.log(2));
wrappedFn(100).then(() => console.log(3));
// 输出 3
「取消」和「忽略」的比较