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

常常听人说:光写业务没啥提高,业务代码能实现需求就行等等,久而久之,咱们就会被繁杂的需求搞得精疲力尽,没有时间学习,待了一段时间感受没啥提高就换一家。这样的恶性循环很不利于咱们自身的职业发展。前端

那么,既然知道会有这样的状况发生,咱们要作的就是如何避免。经过代码锻炼咱们的思惟能力,在工做中提高本身,时间到了,天然进了大厂,工资翻倍!web

可是今天这篇文章不是教大家如何作,只是分享一下我在写业务时的思考,是如何从 if else 中想到了中间件后端

需求

有一个需求:promise

批量开启一个功能,后端会在开启的时候作一个校验,把不成功的列表抛出来,前端作展现,校验会有两种状况,因此一个接口里返回了两个字段,而这两个字段都不必定必返。分如下状况:浏览器

  • 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 作了简化,只是把大体的逻辑展现出来了,实际上的弹窗比这复杂不少。markdown

思考

能够看到这样的代码很是冗余,有大量的重复代码,并且一不当心漏了哪一个判断逻辑仍是致使 bug 的产生。做为工程师,咱们就应该以工程化的思想去解决问题,if 写多了就以为这段代码没意思了,若是之后要加一些其余的逻辑,确定要动到如今的代码,人家不懂这个需求都不知道从哪里下手。我写代码有一个原则就是尽可能不动或者少动以前的代码,这样出现的 bug 也会少,因此在写代码的时候就应该考虑到兼容性和可复用性。app

好了,既然不知足冗余的 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

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

总结

其实咱们平常工做中有不少代码均可以优化,只是疲于需求,赶时间上线,不少地方只是业务上实现了就行,优化永远是留给下一次,因此在平时仍是要严格要求本身,不仅是知足于业务实现,多想一下复用性、扩展性还有高性能,时间长了,代码的水平天然就提上来了!