连续多步骤业务流程的暂停、中断和恢复
背景
用户点击预订按钮,会进入预订流程,可以将其粗略的总结为 4 个步骤:1.表单验证;2.乘机人校验;3.创建订单;4.订单校验。
其业务特点是:
1)以时间为序顺序执行;
2)阻塞执行,即前一步骤执行完毕后,才能执行后一步骤;
3)任一步骤出错,流程终止。
本项目基于 Vue.js 进行开发
基本功能
有了上面的背景铺垫,我们很容易实现预订逻辑,伪代码如下:
async function verifyFormData() {}
async function verifyPassengers() {}
async function createBookOrder() {}
async function verifyBookOrder() {}
async function tapBookSubmit() {
await verifyFormData()
await verifyPassengers()
await createBookOrder()
await verifyBookOrder()
...
}
简洁明了,非常棒! but,新的需求很快就来了。
新的需求
从实际的体验中,我们发现预订的时间太长,除了傻傻的等待,什么也做不了,这能忍?所以我们决定从两个方面进行优化:一方面要求后台加快响应速度,另一方面则是优化交互。自然,落在前端头上的任务,就是优化交互流程了。
需求:预订 loading 弹窗增加关闭按钮,用户点击关闭,弹出二次确认框(含继续、取消两个按钮),点击继续则继续流程,点击取消则终止本次预订(如果创单成功,还需调用取消订单接口取消订单),用户未操作,则等待。
实现 version-1
因为新增需求并不影响原来的逻辑,只是对它们进行了一次增强,这其实是装饰器干的活儿。然而现阶段要使用装饰器,还得额外引入新的编译流程,杀鸡焉用牛刀?索性就用高阶函数改造一下:
async function bookDecorator(fn) {
return async (...args) => {
if (this.BookingModal.isPaused) {
// TODO: 暂停超过3min钟,结束本次预订
await sleep(0.8);
return bookingDecorator(fn)(...args);
}
if (this.BookingModal.isAborted) {
// 取消订单
if (this.__canCancelOrder) {
await cancelOrder();
}
throw "用户取消了本次预订";
}
return fn.call(this, ...args);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function tapBookSubmit() {
await bookDecorator(verifyFormData)()
await bookDecorator(verifyPassengers)()
await bookDecorator(createBookOrder)()
await bookDecorator(verifyBookOrder)()
...
}
仅更改了少量代码,我们就完成了新的需求,感觉还不错~
实现 version-2
version-1 的实现有两个瑕疵:1)tapBookSubmit 函数中有重复代码;2)bookDecorator 及内部的递归每次都会创建新的函数,有爆栈的风险。对此,我们进行一点优化
async function runSequence(arr) {
let count = 0
let res = null
while(true) {
if (!arr[count]) return res
if (this.BookingModal.isPaused) {
await sleep(0.8);
} else if (this.BookingModal.isAborted) {
// 取消订单
if (this.__canCancelOrder) {
await cancelOrder();
}
throw "用户取消了本次预订";
} else {
res = await arr[count++](res)
}
}
}
async function tapBookSubmit() {
const res = await runSequence([
verifyFormData,
verifyPassengers,
createBookOrder,
verifyBookOrder
])
...
}