一、背景
在我进行常规业务开发的时候遇到了一个这样的情景,在我需要进行某项操作的时候,比如说点击一个按钮,然后点击这个按钮后会发起一个请求,而在发起这个请求之前,我需要先进行一项效验工作,而这个效验工作也是一个异步的网络请求。如果有冲突或者其他问题,那么我需要进行弹窗向由用户来确认是否要继续进行这个操作,那么问题来了,弹窗之后的回调结果是由用户来控制触发的,比如iview的modal点击确认按钮的回调事件为"on-ok",因此我们并不能马上知道用户的决定,而我的做法是在"on-ok"的回调中去进行下一步操作。
二、问题
但是这样做会产生一些奇怪的问题,首先需要维护很多的变量,比如说,什么情况,打开弹窗后的确定是这个事件引起的,什么情况下打开的弹窗是那个事件引起的,这些都需要相应的变量来空,也称为开关变量。这样做的话确实是能解决这个问题的,但是不好理解和维护,如果事件很多,很多地方都需要这个弹窗,而且每个事件都是独立的,那么在''on-ok''这个事件回调里就会有很多的代码,而且与相应的事件强耦合,无法独立。比如:
onOk() {
if (eventConfirm1) {
// 如果是事件一的确认那么...
} else if (eventConfirm2) {
// 如果是事件二的确认那么...
} else if (eventConfirm3) {
// 如果是事件三的确认那么...
}
// ...
}
可以见得代码逻辑性很强,但是复用性很差,而且在开发中,代码复用是高质量代码的体现,经过一番思考,我想了一个可以解决问题,但是可能会违背Promise设计理念的方法。
三、基于Promise外部决议的探讨
对于以上问题,我想了一想,无非就是对于未来结果我们的不可知,也就是''on-ok''事件我们不知道在未来何时会调用,无法控制,因此只能将确认后的代码写在''on-ok''的事件回调里。因此,可不可以更为优雅的解决这个问题呢?答案是可以的,例如以上问题我们可以这样做:
onOk() {
// outerResolve为一个全局对象,用于保存某一个事件的成功resolve
this.outerResolve('决议了!');
}
eventConfirm1() {
return new Promise((resolve) => {
if ('需要用户确认') {
// 打开弹窗
this.outerResolve = resolve;
}
})
}
那么在需要调用eventConfirm1的函数中,通过async,await关键字来处理,await后面跟的就是eventConfirm1因此当弹窗弹出后,到等待用户确认的这段时间中,await eventConfirm1后面的代码都不会执行,一旦用户确认了,promise决议,那么await eventConfirm1后面的代码才会执行 。这下我们就可以在await eventConfirm1后面执行用户确认后的代码。而且也保持了''on-ok"里面的代码的整洁性,而且因为基本上用户操作的单一性,在弹窗的时候大多是是对应一个事件的,所以outerResolve的决议也对应该事件。
// 简单地测试代码
let outerResolve = null;
let promise = new Promise((resolve) => {
outerResolve = resolve;
})
async function test() {
await promise;
console.log(123);
}
test()
outerResolve();
四、总结
对于这种方式确实保持了代码在逻辑上的同步,更符合人的大脑思维,但是Promise的状态一般是内部改变的,而不是放到外部执行。因为控制回调的控制权应当掌握在内部而不是外部,否则控制反转,不符合Promise设计原则。
那么其实更好的设计是把outerResolve换成一个回调函数,在效验的时候把具体的函数赋值给它,然后在onOk内调用。