從業務中學到的中間件思想

常常聽人說:光寫業務沒啥提高,業務代碼能實現需求就行等等,久而久之,咱們就會被繁雜的需求搞得精疲力盡,沒有時間學習,待了一段時間感受沒啥提高就換一家。這樣的惡性循環很不利於咱們自身的職業發展。前端

那麼,既然知道會有這樣的狀況發生,咱們要作的就是如何避免。經過代碼鍛鍊咱們的思惟能力,在工做中提高本身,時間到了,天然進了大廠,工資翻倍!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

其實這裏的實現只是簡單的一段,你們平時能夠多去了解一下知名庫和框架的源碼,裏面有不少值得學習的東西。看多了有一些天然就知道了,並且如今前端不少概念都是搬的後端的,有時間也瞭解一下後端的東西,會對咱們的職業生涯有很大幫助,說不定哪天你把後端的概念搬到了前端,那你就是業界大佬了!🤪

總結

其實咱們平常工做中有不少代碼均可以優化,只是疲於需求,趕時間上線,不少地方只是業務上實現了就行,優化永遠是留給下一次,因此在平時仍是要嚴格要求本身,不僅是知足於業務實現,多想一下複用性、擴展性還有高性能,時間長了,代碼的水平天然就提上來了!