34.promise-竞态问题
# 前言
# 竞态问题解决思路
解决思路有两个:
- 使用内置
api
去实质性的取消过期请求。XMLHttpRequest
取消请求fetch API
取消请求axios
取消请求
- 可取消的
promise
请求。- 闭包 + 指令式
promise
(每次请求时,取消上一次的promise
) - 闭包+ 使用
id
标识promise
- 闭包 + 指令式
# 内置 api
取消请求
# XMLHttpRequest
如果请求已被发出,可以使用
abort()
方法立刻中止请求。
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://xxx");
xhr.send();
xhr.abort(); // 取消请求
1
2
3
4
5
6
2
3
4
5
6
# fetch API
取消请求
需要使用
AbortController
const controller = new AbortController();
const signal = controller.signal;
fetch("/xxx", {
signal,
}).then(function(response) {
//...
});
controller.abort(); // 取消请求
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# axios
取消请求
在
v0.22.0
后,axios
支持以fetch API
方式的AbortController
取消请求。
const controller = new AbortController();
axios
.get("/xxx", {
signal: controller.signal,
})
.then(function(response) {
//...
});
controller.abort(); // 取消请求
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
扩展:AbortSignal
也可扩展在 node
维护的各种 api
中
import { readFile } from "node:fs/promises";
try {
const controller = new AbortController();
const { signal } = controller;
const promise = readFile(fileName, { signal });
// Abort the request before the promise settles.
controller.abort();
await promise;
} catch (err) {
// When a request is aborted - err is an AbortError
console.error(err);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 取消 promise
请求
# 指令式 promise
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用唯一 id
标识每次请求
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
测试代码如下:
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
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 参考文章:
编辑 (opens new window)
上次更新: 2023/10/02, 17:10:00