从业务中学到的中间件思想

2,430 阅读7分钟

经常听人说:光写业务没啥提升,业务代码能实现需求就行等等,长此以往,我们就会被繁杂的需求搞得精疲力尽,没有时间学习,待了一段时间感觉没啥提升就换一家。这样的恶性循环很不利于我们自身的职业发展。

那么,既然知道会有这样的情况发生,我们要做的就是如何避免。通过代码锻炼我们的思维能力,在工作中提升自己,时间到了,自然进了大厂,工资翻倍!

但是今天这篇文章不是教你们如何做,只是分享一下我在写业务时的思考,是如何从 if else 中想到了中间件

需求

有一个需求:

批量开启一个功能,后端会在开启的时候做一个校验,把不成功的列表抛出来,前端做展示,校验会有两种情况,所以一个接口里返回了两个字段,而这两个字段都不一定必返。分以下情况:

  • A、B 都有
  • A 有 B 没有
  • B 有 A 没有
  • A、B 都没有

而交互想要的效果是这样的:

  • A、B 都有:先弹 A 弹窗,点击确定后再弹 B 弹窗,关闭 B 后提示完成
  • A 有 B 没有:弹 A 弹窗,关闭后提示完成
  • B 有 A 没有:弹 B 弹窗,关闭后提示完成
  • A、B 都没有:提示完成

大家可以先想一下如果你们接到这样的需求会怎么实现,正常的思路就是 if 判断了

  • 如果两个条件都有,先展示第一个,点击关闭后再展示第二个,关闭第二个之后展示提示
  • 如果只有一个条件,那就展示指定条件下的弹窗,关闭后展示提示
  • 如果没有条件就不展示弹窗,直接展示提示

Demo

const { A = [], B = [] } = res.result;

if (A.length > 0 && B.length > 0) {
  Modal1.info({
    onOk: () => {
      Modal2.info({
        onOk: () => message.success('提示')
      })
    }
  });
  return;
}

if (A.length > 0) {
  Modal1.info({
    onOk: () => message.success('提示')
  });
  return;
}

if (B.length > 0) {
    Modal2.info({
    onOk: () => message.success('提示')
  });
  return;
}

message.success('提示');

这里的 Demo 做了简化,只是把大致的逻辑展示出来了,实际上的弹窗比这复杂很多。

思考

可以看到这样的代码非常冗余,有大量的重复代码,而且一不小心漏了哪个判断逻辑还是导致 bug 的产生。作为工程师,我们就应该以工程化的思想去解决问题,if 写多了就觉得这段代码没意思了,如果以后要加一些其他的逻辑,肯定要动到现在的代码,人家不懂这个需求都不知道从哪里下手。我写代码有一个原则就是尽量不动或者少动之前的代码,这样出现的 bug 也会少,所以在写代码的时候就应该考虑到兼容性和可复用性。

好了,既然不满足冗余的 if else 和大量重复的代码,就要想办法解决呀,于是就开始了加班想办法。。

Promise

起初想的是使用 Promise 来解决,因为 Promise 有一个 all 方法,可以在弹窗关闭的时候将 Promise 的状态置为 resolved,通过 Promise.all([p1, p2]) 得到所有弹窗打开和关闭的状态,就能够在所有弹窗关闭的时候提示已完成。

但是这样又有一个问题没办法解决,就是顺序显示的问题,虽然 Promise 容器里面的任务是异步的,但是 Promise 的执行缺是同步的,就会出现弹窗一起出现的问题。且看下面的 Demo

Demo

const { A = [], B = [] } = res.result;
const promises = [];

if (A.length > 0) {
  const p1 = new Promise((resolve) => {
    Modal1.info({
      onOk: () => resolve()
    });
  })
  promises.push(p1);
}

if (B.length > 0) {
  const p2 = new Promise((resolve) => {
    Modal2.info({
      onOk: () => resolve()
    });
  })
  promises.push(p2);
}

Promise.all(promises).then(() => {
  message.success('提示');
})

可以看到这里已经解决了弹窗结束时的提示,并且不需要多少逻辑判断,每个弹窗独立的逻辑,互不影响,就算以后再加弹窗和逻辑,也基本上不会影响到之前的业务,所以到这里业务隔离和逻辑独立基本上实现了,但是最重要的一个功能没法实现,就是弹窗的依次显示,关闭上一个后再显示下一个。

目前的这个 Demo 是不管你有多少个弹窗,只要你显示就全部展示出来,要真是这样交给交互,那她一定坐不住了,跑过来跟你这啊那啊的。。。没办法,继续加班吧~

中间件

中间件这个名次近几年在前端的出现也越来越频繁,从 Express 到 Koa 再到 Redux 等的,前端的各种工具,库和框架都越来越多的使用到了中间件的思想。

这里简单说一下 Express 中使用到的中间件。Express 是 Node 的框架,能够帮助快速搭建一个 web 服务器。Express 中的 app.use 就是我们所说的使用中间件。那么中间件究竟是干什么的呢?

我们从浏览器发送请求需要在服务器中进行一系列的处理解析才能得到最终我们想要的数据,这一系列的处理就是中间件的任务,例如:

  • 获取 get 请求体中的数据
  • 获取 post 请求体中的数据
  • 解析 Cookie
  • 解析 Session
  • ...

这些都可以让中间件帮我们完成,以至于我们可以专注于业务。

app.use(function (req, res, next) {
  //...
  // 如果不调用 next 中间件就不会继续往下面执行了
  // next() 就是用来调用下一个匹配中间件的
  next();
})

好了,有了一点的前置知识就需要继续来搞我们的业务了。知道了中间件是干什么的,就需要拿它来干点实事了。

再来深挖一下我们想要实现的功能,其实就是任务处理机制:后一个任务需要等到前一个处理完再执行,而前一个任务的存在性又不确定,确定了这个就很好去实现它了。

Demo

const { A = [], B = [] } = res.result;
const taskQueue = [];

if (A.length > 0) {
  // 创建一个子任务加入到任务队列
  // 完成的时候调用 next 开始下一个任务
  const subtask = (next) => {
    Modal1.info({
      onOk: () => next()
    });
  }
  taskQueue.push(subtask);
}

if (B.length > 0) {
  const subtask = (next) => {
    Modal2.info({
      onOk: () => next()
    });
  }
  taskQueue.push(subtask);
}

// 这一段是核心代码
const next = () => {
  // 弹出任务队列中的第一个任务
  const subtask = taskQueue.shift();
  
  // 如果存在就让它执行,并传入 next
  // 不存在就表示任务队列为空了,这个时候就可以触发结束事件了
  if (subtask) {
    subtask(next);
  } else {
    message.success('提示');
  }
}

next();

如果你平时有看源码的习惯,上面的这一段核心代码可以从很多的源码中都能找到,包括现在的前端框架 Vue 和 React 里也有这些影子,Webpack 的 loader 所做的事情也和这个差不多,由一个特定的 loader 处理文件,再将处理后的结果传递给下一个 loader,本质上都是任务处理。

start => subtask1 => subtask2 => ... => end

其实这里的实现只是简单的一段,大家平时可以多去了解一下知名库和框架的源码,里面有很多值得学习的东西。看多了有一些自然就知道了,而且现在前端很多概念都是搬的后端的,有时间也了解一下后端的东西,会对我们的职业生涯有很大帮助,说不定哪天你把后端的概念搬到了前端,那你就是业界大佬了!🤪

总结

其实我们日常工作中有很多代码都可以优化,只是疲于需求,赶时间上线,很多地方只是业务上实现了就行,优化永远是留给下一次,所以在平时还是要严格要求自己,不只是满足于业务实现,多想一下复用性、扩展性还有高性能,时间长了,代码的水平自然就提上来了!