连续多步骤业务流程的暂停、中断和恢复

背景

用户点击预订按钮,会进入预订流程,可以将其粗略的总结为 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
  ])
  ...
}
posted @ 2020-08-11 11:43  Liaofy  阅读(554)  评论(0编辑  收藏  举报