投身烈火
Keep Walking

如何终止promise

起因

最近在工作中遇到一个问题,在 promise 的链式调用中,涉及到一个类似 break 的操作。就是在某一个 then 函数的调用中,某种情况下,要取消后续的所有操作。于是调查了下 promise 的 api,想找到实现类似操作的方法。但是在后续的调查中,我发现 ———— promise 根本就没办法终止后续的操作……

原因

promise 的设计,本质上是一个状态机。从初始状态,到有了结果的 reject/resolve 状态,整个过程的变化的单向的。所以,promise 根本没有所谓的取消状态。

实现

抛开 promise 设计上是否有缺陷不谈,那么使用 promise 时是否可以实现终止操作呢?其实通过 promise 本身的机制,配合一定编码规范,是可以实现的。

我们都知道 promise 的 then 方法可以接受两个函数,一个用来处理 reject,一个用来处理 resolve,如果一个 then 方法里面发生异常,就会执行下一个有 reject 处理函数的 then 里面的 reject 处理 函数,catch 只是 then(null, rejectHandler) 的一种封装而已。

比如下面这个例子,其中由于 p2 的那个 then 方法中没有 reject 处理函数,所以被跳过了。

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
function start() {
return new Promise((resolve, reject) => {
resolve('start');
});
}
start()
.then(data => {
console.log('resolve,result of start: ', data);
return Promise.reject(1); // p1
})
.then(data => {
console.log('resolve,result of p1: ', data);
return Promise.reject(2); // p2
})
.then(data => {
console.log('result of p2: ', data);
return Promise.resolve(3); // p3
}, data => {
console.log('reject, result of p1: ', data);
return Promise.reject(3); // p3
})
.catch(ex => {
console.log('ex: ', ex);
return Promise.resolve(4); // p4
})
.then(data => {
console.log('result of p4: ', data);
});

所以,如果我们只向 then 方法中传入 resolve 处理函数,在 promise 调用链的最后加上 catch,把所有的错误处理都放在 catch 里面,想要 break 的时候抛出一个固定的错误,就能达到终止操作的效果了。

不过这样也一方面不好,就是在无法在 then 中做异常处理,所以需要对 then 和 catch 进行下封装,另外如果每次都要写抛出错误不太方便,我简单封装了一下,代码如下。

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
52
53
54
55
56
57
58
function wrapThenRejectHandler(rejectHandler) {
return function(e) {
if (e instanceof Error && e.message === 'break' || typeof rejectHandler === 'undefined') {
return Promise.reject(e);
} else {
return rejectHandler(e);
}
};
}
function wrapCatchRejectHandler(rejectHandler) {
return function (e) {
if (!(e instanceof Error && e.message === 'break') && rejectHandler) {
rejectHandler(e);
} else {
return false;
}
}
}
Object.defineProperty(Promise, 'break', {
get: function(){
throw new Error('break');
},
});
let oldThen = Promise.prototype.then;
let oldCatch = Promise.prototype.catch;
Promise.prototype._then = function (resolveHandler, rejectHandler) {
return oldThen.bind(this)(resolveHandler, wrapThenRejectHandler(rejectHandler));
};
Promise.prototype._catch = function (rejectHandler) {
return oldCatch.bind(this)(wrapCatchRejectHandler(rejectHandler));
};
function start() {
return new Promise(function (resolve, reject) {
resolve('start');
});
}
start()
._then(function () {
console.log('1');
})
._then(function () {
console.log('2');
Promise.break;
})
._then(function () {
console.log('3');
}, function () {
console.log('3,error')
})
._catch(function (ex) {
console.log('ex: ', ex);
});

封装的过程中发现个问题,如果新的方法沿用then或者catch的名字,会造成死循环的情况,不知道为毛……

题外话……

有人评论说无法取消是 promise 的一个缺点。我倒是觉得 promise 自设计之初就不适合用来处理有取消状态的场景。因为取消状态的加入会让问题变得异常复杂。

举例来说,有个用户在电商系统上下单购买了一件商品(初始状态),在商家发货之前(resolve状态),因为没钱取消了订单(取消状态),取消订单需要审核(取消状态的resolve状态),审核期间用户发工资了又不想取消订单了,于是又点了【取消退订】(取消状态的取消状态),而【取消退订】也是异步的,你还能取消【取消退订】……如果要用 promise 实现这个逻辑,估计最后会死的很惨吧……

好的由于时间不足,本期的博客就先写到这里,如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的热情了~!

白了个白~!