32.Promise-指令式Promise改造
# 0.前言
为什么要使用指令式的回调?传统的 Promise
使用时,resolve/reject
只能在 new Promise
内部使用,指令式支持在 Promise
定义区间的外部去调用指令。
# 1. 指令式回调的思想
指令式回调:可通过变量赋值的方式将函数内的变量提取到函数外部。
let fun = null;
function obtainInnerVar() {
const resolve = () => {};
fun = resolve;
}
// 执行函数后,可将函数内部的变量提升到外部作用域
obtainInnerVar();
fun && fun();
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 2. Pomise 指令式写法
以 awesome-imperative-promise 源码为例,仅 40 行就是实现了指令式 Promise
改造。
代码逻辑梳理:
只需要想通一个问题,即老的
Promise(promiseFun)
与新的Promise(WrappedPromise)
之间是如何联系的,正常情况下只能通过嵌套完成,在promiseFun
的外部再包一个更大的new Promise
(叫WrappedPromise
也合适)。如果不使用嵌套,则可利用一个中间变量将新Promise
的resolve
和reject
提取出来嫁接给旧的Promise
函数执行。下述案例中,老
Promise
,新Promise
,和中转变量 (resolve/reject
) 处于同一层作用域。
/* resolve 既有可能接受结果,也可以接受 Promise 类型 */
type ResolveCallback<T> = (value: T | PromiseLike<T>) => void;
type RejectCallback = (reason?: any) => void;
type CancelCallback = () => void;
export type ImperativePromise<T> = {
promise: Promise<T>;
resolve: ResolveCallback<T>;
reject: RejectCallback;
cancel: CancelCallback;
};
export function createImperativePromise<T>(
promiseFun?: Promise<T>,
): ImperativePromise<T> {
/* 将传入的 Promise 的 resolve/reject 继承到下面的变量中 */
let resolve: ResolveCallback<T> | null = null;
let reject: RejectCallback | null = null;
/* 将回调内的变量(这里为 resolve/reject 函数)给拿到外部 */
/* 以下代码为立即执行的: */
const wrappedPromise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
/* 异步执行,有可能为 null */
promiseFun &&
promiseFun.then(
(val) => {
resolve && resolve(val);
},
(error) => {
reject && reject(error);
},
);
return {
promise: wrappedPromise,
resolve: (value: T | PromiseLike<T>) => {
resolve && resolve(value);
},
reject: (reason?: any) => {
reject && reject(reason);
},
cancel: () => {
resolve = null;
reject = null;
},
};
}
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
注:上述代码中提供出的 cancel
只是忽略对结果的处理,不会将已发送的请求(如:网络请求)主动取消。
使用方式如下:
const { promise, resolve, reject, cancel } = createImperativePromise(promise);
resolve("some value");
reject(new Error(":s"));
cancel(); // 保证 promise 没有 resolve 或者 reject 返回结果
1
2
3
4
2
3
4
# 3. 应用:指令式回调+闭包实现可取消的 promise
在 Promise
中经常会遇到”竞态“问题,同一行为可能会发次多次网络请求,由于网络等原因无法决定哪儿个请求先响应,但是一般情况只希望获取最新的一次异步请求结果,对之前的异步请求进行取消操作。
使用指令式回调可以将 cancel
函数暴露出来,再使用高阶函数将此 cancel
函数提升为闭包变量,主要用途缓存上一次的 promise
对象暴露出的取消函数。在每次发送新请求前,cancel 掉上一次的请求,忽略它的回调。
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
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
测试代码如下:
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
# 4.参考资料
编辑 (opens new window)
上次更新: 2023/10/02, 17:10:00