亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

使用compose函數(shù)優(yōu)化代碼提高可讀性及擴(kuò)展性

 更新時(shí)間:2022年06月14日 16:52:48   作者:掘金安東尼  
這篇文章主要為大家介紹了使用compose函數(shù)提高代碼可讀性及擴(kuò)展性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

本瓜知道前不久寫的《JS 如何函數(shù)式編程》系列各位可能并不感冒,因?yàn)橐磺欣碚摰臇|西如果脫離實(shí)戰(zhàn)的話,那就將毫無意義。

于是乎,本瓜著手于實(shí)際工作開發(fā),嘗試應(yīng)用函數(shù)式編程的一些思想。

最終驚人的發(fā)現(xiàn):這個(gè)實(shí)現(xiàn)過程并不難,但是效果卻不??!

實(shí)現(xiàn)思路:借助 compose 函數(shù)對(duì)連續(xù)的異步過程進(jìn)行組裝,不同的組合方式實(shí)現(xiàn)不同的業(yè)務(wù)流程。

這樣不僅提高了代碼的可讀性,還提高了代碼的擴(kuò)展性。我想:這也許就是高內(nèi)聚、低耦合吧~

撰此篇記之,并與各位分享。

場(chǎng)景說明

在和產(chǎn)品第一次溝通了需求后,我理解需要實(shí)現(xiàn)一個(gè)應(yīng)用 新建流程,具體是這樣的:

  • 第 1 步:調(diào)用 sso 接口,拿到返回結(jié)果 res_token;
  • 第 2 步:調(diào)用 create 接口,拿到返回結(jié)果 res_id;
  • 第 3 步:處理字符串,拼接 Url;
  • 第 4 步:建立 websocket 鏈接;
  • 第 5 步:拿到 websocket 后端推送關(guān)鍵字,渲染頁面;

注:接口、參數(shù)有做一定簡(jiǎn)化

上面除了第 3 步、第 5 步,剩下的都是要調(diào)接口的,并且前后步驟都有傳參的需要,可以理解為一個(gè)連續(xù)且有序的異步調(diào)用過程。

為了快速響應(yīng)產(chǎn)品需求,于是本瓜迅速寫出了以下代碼:

/**
 * 新建流程
 * @param {*} appId
 * @param {*} tag
 */
export const handleGetIframeSrc = function(appId, tag) {
  let h5Id
// 第 1 步: 調(diào)用 sso 接口,獲取token
  getsingleSignOnToken({ formSource: tag }).then(data => { 
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => { 
    const para = { appId: appId }
    return new Promise((resolve, reject) => {
// 第 2 步: 調(diào)用 create 接口,新建應(yīng)用
      appH5create(para).then(res => {
// 第 3 步: 處理字符串,拼接 Url
        this.handleInsIframeUrl(res, token, appId)
        this.setH5Id(res.result.h5Id)
        h5Id = res.result.h5Id
        resolve(h5Id)
      }).catch(err => {
        this.$message({
          message: err.message || '出現(xiàn)錯(cuò)誤',
          type: 'error'
        })
      })
    })
  }).then(h5Id => { 
// 第 4 步:建立 websocket 鏈接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, h5Id)
    })
  }).then(doclose => {
// 第 5 步:拿到 websocket 后端推送關(guān)鍵字,渲染頁面;
    if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出現(xiàn)錯(cuò)誤',
      type: 'error'
    })
  })
}
const handleInsIframeUrl = function(res, token, appId) { 
// url 拼接
  const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
  let editUrl = res.result.editUrl
  const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
  editUrl = res.result.editUrl.replace(infoId, `from=a2p&${infoId}`)
  const headList = JSON.parse(JSON.stringify(this.headList))
  headList.forEach(i => {
    if (i.appId === appId) { i.srcUrl = `${editUrl}&token=${token}&secretId=${secretId}` }
  })
  this.setHeadList(headList)
}

這段代碼是非常自然地根據(jù)產(chǎn)品所提需求,然后自己理解所編寫。

其實(shí)還可以,是吧???

需求更新

但你不得不承認(rèn),程序員和產(chǎn)品之間有一條無法逾越的溝通鴻溝。

它大部分是由所站角度不同而產(chǎn)生,只能說:李姐李姐!

所以,基于前一個(gè)場(chǎng)景,需求發(fā)生了點(diǎn) 更新 ~

除了上節(jié)所提的 【新建流程】 ,還要加一個(gè) 【編輯流程】 ╮(╯▽╰)╭

編輯流程簡(jiǎn)單來說就是:砍掉新建流程的第 2 步調(diào)接口,再稍微調(diào)整傳參即可。

于是本瓜直接 copy 一下再作簡(jiǎn)單刪改,不到 1 分鐘,編輯流程的代碼就誕生了~

/**
 * 編輯流程
 */
const handleToIframeEdit = function() { // 編輯 iframe
  const { editUrl, appId, h5Id } = this.ruleForm
// 第 1 步: 調(diào)用 sso 接口,獲取token
  getsingleSignOnToken({ formSource: 'ins' }).then(data => {
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => { 
// 第 2 步:處理字符串,拼接 Url
    return new Promise((resolve, reject) => {
      const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
      const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
      const URL = editUrl.replace(infoId, `from=a2p&${infoId}`)
      const headList = JSON.parse(JSON.stringify(this.headList))
      headList.forEach(i => {
        if (i.appId === appId) { i.srcUrl = `${URL}&token=${token}&secretId=${secretId}` }
      })
      this.setHeadList(headList)
      this.setShowEditLink({ appId: appId, h5Id: h5Id, state: false })
      this.setShowNavIframe({ appId: appId, state: true })
      this.setNavLabel(this.headList.find(i => i.appId === appId).name)
      resolve(h5Id)
    })
  }).then(h5Id => {
// 第 3 步:建立 websocket 鏈接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, h5Id)
    })
  }).then(doclose => {
// 第 4 步:拿到 websocket 后端推送關(guān)鍵字,渲染頁面;
    if (doclose) { this.setShowEditLink({ appId: appId, h5Id: h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出現(xiàn)錯(cuò)誤',
      type: 'error'
    })
  })
}

需求再更新

老實(shí)講,不怪產(chǎn)品,咱做需求的過程也是逐步理解需求的過程。理解有變化,再正常不過!(#^.^#) 李姐李姐......

上面已有兩個(gè)流程:新建流程、編輯流程。

這次,要再加一個(gè) 重新創(chuàng)建流程 ~

重新創(chuàng)建流程可簡(jiǎn)單理解為:在新建流程之前調(diào)一個(gè) delDraft 刪除草稿接口;

至此,我們產(chǎn)生了三個(gè)流程:

  • 新建流程;
  • 編輯流程;
  • 重新創(chuàng)建流程;

本瓜這里作個(gè)簡(jiǎn)單的腦圖示意邏輯:

我的直覺告訴我:不能再 copy 一份新建流程作修改了,因?yàn)檫@樣就太拉了。。。沒錯(cuò),它沒有耦合,但是它也沒有內(nèi)聚,這不是我想要的。于是,我開始封裝了......

實(shí)現(xiàn)上述腦圖的代碼:

/**
 * 判斷是否存在草稿記錄?
 */
judgeIfDraftExist(item) {
  const para = { appId: item.appId }
  return appH5ifDraftExist(para).then(res => {
    const { editUrl, h5Id, version } = res.result
    if (h5Id === -1) { // 不存在草稿
      this.handleGetIframeSrc(item)
    } else { // 存在草稿
      this.handleExitDraft(item, h5Id, version, editUrl)
    }
  }).catch(err => {
    console.log(err)
  })
},
/**
 * 選擇繼續(xù)編輯?
 */
handleExitDraft(item, h5Id, version, editUrl) {
  this.$confirm('有未完成的信息收集鏈接,是否繼續(xù)編輯?', '提示', {
    confirmButtonText: '繼續(xù)編輯',
    cancelButtonText: '重新創(chuàng)建',
    type: 'warning'
  }).then(() => {
    const editUrlH5Id = h5Id
    this.handleGetIframeSrc(item, editUrl, editUrlH5Id)
  }).catch(() => {
    this.handleGetIframeSrc(item)
    appH5delete({ h5Id: h5Id, version: version })
  })
},
/**
 * 新建流程、編輯流程、重新創(chuàng)建流程;
 */
handleGetIframeSrc(item, editUrl, editUrlH5Id) {
  let ws_h5Id
  getsingleSignOnToken({ formSource: item.tag }).then(data => { 
// 調(diào)用 sso 接口,拿到返回結(jié)果 res_token;
    return new Promise((resolve, reject) => {
      resolve(data.result)
    })
  }).then(token => {
    const para = { appId: item.appId }
    return new Promise((resolve, reject) => {
      if (!editUrl) { // 新建流程、重新創(chuàng)建流程
// 調(diào)用 create 接口,拿到返回結(jié)果 res_id;
        appH5create(para).then(res => {
// 處理字符串,拼接 Url;
          this.handleInsIframeUrl(res.result.editUrl, token, item.appId)
          this.setH5Id(res.result.h5Id)
          ws_h5Id = res.result.h5Id
          this.setShowNavIframe({ appId: item.appId, state: true })
          this.setNavLabel(item.name)
          resolve(true)
        }).catch(err => {
          this.$message({
            message: err.message || '出現(xiàn)錯(cuò)誤',
            type: 'error'
          })
        })
      } else { // 編輯流程
        this.handleInsIframeUrl(editUrl, token, item.appId)
        this.setH5Id(editUrlH5Id)
        ws_h5Id = editUrlH5Id
        this.setShowNavIframe({ appId: item.appId, state: true })
        this.setNavLabel(item.name)
        resolve(true)
      }
    })
  }).then(() => { 
// 建立 websocket 鏈接;
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, ws_h5Id)
    })
  }).then(doclose => {
// 拿到 websocket 后端推送關(guān)鍵字,渲染頁面;
    if (doclose) { this.setShowEditLink({ appId: item.appId, h5Id: ws_h5Id, state: true }) }
  }).catch(err => {
    this.$message({
      message: err.message || '出現(xiàn)錯(cuò)誤',
      type: 'error'
    })
  })
},
handleInsIframeUrl(editUrl, token, appId) {
// url 拼接
  const secretId = this.$store.state.userinfo.enterpriseList[0].secretId
  const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
  const url = editUrl.replace(infoId, `from=a2p&${infoId}`)
  const headList = JSON.parse(JSON.stringify(this.headList))
  headList.forEach(i => {
    if (i.appId === appId) { i.srcUrl = `${url}&token=${token}&secretId=${secretId}` }
  })
  this.setHeadList(headList)
}

如此,我們便將 新建流程、編輯流程、重新創(chuàng)建流程 全部整合到了上述代碼;

需求再再更新

上面的封裝看起來似乎還不錯(cuò),但是這時(shí)我害怕了!想到:如果這個(gè)時(shí)候,還要加流程或者改流程呢??? 我是打算繼續(xù)用 if...else 疊加在那個(gè)主函數(shù)里面嗎?還是打算直接 copy 一份再作刪改?

我都能遇見它會(huì)充斥著各種判斷,變量賦值、引用飛來飛去,最終成為一坨??,沒錯(cuò),代碼屎山的??

我摸了摸左胸的左心房,它告訴我:“饒了接盤俠吧~”

于是乎,本瓜嘗試引進(jìn)了之前吹那么 nb 的函數(shù)式編程!它的能力就是讓代碼更可讀,這是我所需要的!來吧??!展示??!

compose 函數(shù)

我們?cè)?《XDM,JS如何函數(shù)式編程?看這就夠了?。ㄈ?/a> 這篇講過函數(shù)組合 compose!沒錯(cuò),我們這次就要用到這個(gè)家伙!

還記得那句話嗎?

組合 ———— 聲明式數(shù)據(jù)流 ———— 是支撐函數(shù)式編程最重要的工具之一!

最基礎(chǔ)的 compose 函數(shù)是這樣的:

function compose(...fns) {
    return function composed(result){
        // 拷貝一份保存函數(shù)的數(shù)組
        var list = fns.slice();
        while (list.length > 0) {
            // 將最后一個(gè)函數(shù)從列表尾部拿出
            // 并執(zhí)行它
            result = list.pop()( result );
        }
        return result;
    };
}
// ES6 箭頭函數(shù)形式寫法
var compose =
    (...fns) =>
        result => {
            var list = fns.slice();
            while (list.length > 0) {
                // 將最后一個(gè)函數(shù)從列表尾部拿出
                // 并執(zhí)行它
                result = list.pop()( result );
            }
            return result;
        };

它能將一個(gè)函數(shù)調(diào)用的輸出路由跳轉(zhuǎn)到另一個(gè)函數(shù)的調(diào)用上,然后一直進(jìn)行下去。

我們不需關(guān)注黑盒子里面做了什么,只需關(guān)注:這個(gè)東西(函數(shù))是什么!它需要我輸入什么!它的輸出又是什么!

composePromise

但上面提到的 compose 函數(shù)是組合同步操作,而在本篇的實(shí)戰(zhàn)中,我們需要組合是異步函數(shù)!

于是它被改造成這樣:

/**
 * @param  {...any} args
 * @returns
 */
export const composePromise = function(...args) {
  const init = args.pop()
  return function(...arg) {
    return args.reverse().reduce(function(sequence, func) {
      return sequence.then(function(result) {
        // eslint-disable-next-line no-useless-call
        return func.call(null, result)
      })
    }, Promise.resolve(init.apply(null, arg)))
  }
}

原理:Promise 可以指定一個(gè) sequence,來規(guī)定一個(gè)執(zhí)行 then 的過程,then 函數(shù)會(huì)等到執(zhí)行完成后,再執(zhí)行下一個(gè) then 的處理。啟動(dòng)sequence 可以使用 Promise.resolve() 這個(gè)函數(shù)。構(gòu)建 sequence 可以使用 reduce 。

我們?cè)賹懸粋€(gè)小測(cè)試在控制臺(tái)跑一下!

let compose = function(...args) {
  const init = args.pop()
  return function(...arg) {
    return args.reverse().reduce(function(sequence, func) {
      return sequence.then(function(result) {
        return func.call(null, result)
      })
    }, Promise.resolve(init.apply(null, arg)))
  }
}
let a = async() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('xhr1')
      resolve('xhr1')
    }, 5000)
  })
}
let b = async() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('xhr2')
      resolve('xhr2')
    }, 3000)
  })
}
let steps = [a, b] // 從右向左執(zhí)行
let composeFn = compose(...steps)
composeFn().then(res => { console.log(666) })
// xhr2
// xhr1
// 666

它會(huì)先執(zhí)行 b ,3 秒后輸出 "xhr2",再執(zhí)行 a,5 秒后輸出 "xhr1",最后輸出 666

你也可以在控制臺(tái)帶參 debugger 試試,很有意思:

composeFn(1, 2).then(res => { console.log(66) })

逐漸美麗起來

測(cè)試通過!借助上面 composePromise 函數(shù),我們更加有信心用函數(shù)式編程 composePromise 重構(gòu) 我們的代碼了。

實(shí)際上,這個(gè)過程一點(diǎn)不費(fèi)力~

實(shí)現(xiàn)如下:

/**
 * 判斷是否存在草稿記錄?
 */
handleJudgeIfDraftExist(item) {
    return appH5ifDraftExist({ appId: item.appId }).then(res => {
      const { editUrl, h5Id, version } = res.result
      h5Id === -1 ? this.compose_newAppIframe(item) : this.hasDraftConfirm(item, h5Id, editUrl, version)
    }).catch(err => {
      console.log(err)
    })
},
/**
 * 選擇繼續(xù)編輯?
 */
hasDraftConfirm(item, h5Id, editUrl, version) {
    this.$confirm('有未完成的信息收集鏈接,是否繼續(xù)編輯?', '提示', {
      confirmButtonText: '繼續(xù)編輯',
      cancelButtonText: '重新創(chuàng)建',
      type: 'warning'
    }).then(() => {
      this.compose_editAppIframe(item, h5Id, editUrl)
    }).catch(() => {
      this.compose_reNewAppIframe(item, h5Id, version)
    })
},

敲黑板啦!畫重點(diǎn)啦!

/**
* 新建應(yīng)用流程
* 入?yún)? item
* 輸出:item
*/
compose_newAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_appH5create, this.step_getsingleSignOnToken]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},
/**
* 編輯應(yīng)用流程
* 入?yún)? item, draftH5Id, editUrl
* 輸出:item
*/
compose_editAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_getsingleSignOnToken]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},
/**
* 重新創(chuàng)建流程
* 入?yún)? item,draftH5Id,version
* 輸出:item
*/
compose_reNewAppIframe(...args) {
    const steps = [this.step_getDoclose, this.step_createWs, this.step_splitUrl, this.step_appH5create, this.step_getsingleSignOnToken, this.step_delDraftH5Id]
    const handleCompose = composePromise(...steps)
    handleCompose(...args)
},

我們通過 composePromise 執(zhí)行不同的 steps,來依次執(zhí)行(從右至左)里面的功能函數(shù);你可以任意組合、增刪或修改 steps 的子項(xiàng),也可以任意組合出新的流程來應(yīng)付產(chǎn)品。并且,它們都被封裝在 compose_xxx 里面,相互獨(dú)立,不會(huì)干擾外界其它流程。同時(shí),傳參也是非常清晰的,輸入是什么!輸出又是什么!一目了然!

對(duì)照腦圖再看此段代碼,不正是對(duì)我們需求實(shí)現(xiàn)的最好詮釋嗎?

對(duì)于一個(gè)閱讀陌生代碼的人來說,你得先告訴他邏輯是怎樣的,然后再告訴他每個(gè)步驟的內(nèi)部具體實(shí)現(xiàn)。這樣才是合理的!

功能函數(shù)(具體步驟內(nèi)部實(shí)現(xiàn)):

/**
* 調(diào)用 sso 接口,拿到返回結(jié)果 res_token;
*/
step_getsingleSignOnToken(...args) {
    const [item] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      getsingleSignOnToken({ formSource: item.tag }).then(data => {
        resolve([...args, data.result]) // data.result 即 token
      })
    })
},
/**
*  調(diào)用 create 接口,拿到返回結(jié)果 res_id;
*/
step_appH5create(...args) {
    const [item, token] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      appH5create({ appId: item.appId }).then(data => {
        resolve([item, data.result.h5Id, data.result.editUrl, token])
      }).catch(err => {
        this.$message({
          message: err.message || '出現(xiàn)錯(cuò)誤',
          type: 'error'
        })
      })
    })
},
/**
* 調(diào) delDraft 刪除接口;
*/
step_delDraftH5Id(...args) {
    const [item, h5Id, version] = args.flat(Infinity)
    return new Promise((resolve, reject) => {
      appH5delete({ h5Id: h5Id, version: version }).then(data => {
        resolve(...args)
      })
    })
},
/**
*  處理字符串,拼接 Url;
*/
step_splitUrl(...args) {
    const [item, h5Id, editUrl, token] = args.flat(Infinity)
    const infoId = editUrl.substr(editUrl.indexOf('?') + 1, editUrl.length - editUrl.indexOf('?'))
    const url = editUrl.replace(infoId, `from=a2p&${infoId}`)
    const headList = JSON.parse(JSON.stringify(this.headList))
    headList.forEach(i => {
      if (i.appId === item.appId) { i.srcUrl = `${url}&token=${token}` }
    })
    this.setHeadList(headList)
    this.setH5Id(h5Id)
    this.setShowNavIframe({ appId: item.appId, state: true })
    this.setNavLabel(item.name)
    return [...args]
},
/**
*  建立 websocket 鏈接;
*/
step_createWs(...args) {
    return new Promise((resolve, reject) => {
      webSocketInit(resolve, reject, ...args) 
})
  },
/**
*  拿到 websocket 后端推送關(guān)鍵字,渲染頁面;
*/
step_getDoclose(...args) {
    const [item, h5Id, editUrl, token, doclose] = args.flat(Infinity)
    if (doclose) { this.setShowEditLink({ appId: item.appId, h5Id: h5Id, state: true }) }
    return new Promise((resolve, reject) => {
      resolve(true)
    })
},

功能函數(shù)的輸入、輸出也是清晰可見的。

至此,我們可以認(rèn)為:借助 compose 函數(shù),借助函數(shù)式編程,咱把業(yè)務(wù)需求流程進(jìn)行了封裝,明確了輸入輸出,讓我們的代碼更加可讀了!可擴(kuò)展性也更高了!這不就是高內(nèi)聚、低耦合?!

階段總結(jié)

你問我什么是 JS 函數(shù)式編程實(shí)戰(zhàn)?我只能說本篇完全就是出自工作中的實(shí)戰(zhàn)!?。?/p>

這樣導(dǎo)致本篇代碼量可能有點(diǎn)多,但是這就是實(shí)打?qū)嵉男枨笞兓?,代碼迭代、改造的過程。(建議通篇把握、理解)

當(dāng)然,這不是終點(diǎn),代碼重構(gòu)這個(gè)過程應(yīng)該是每時(shí)每刻都在進(jìn)行著。

對(duì)于函數(shù)式編程,簡(jiǎn)單應(yīng)用 compose 函數(shù),這也只是一個(gè)起點(diǎn)!

已經(jīng)講過,偏函數(shù)、函數(shù)柯里化、函數(shù)組合、數(shù)組操作、時(shí)間狀態(tài)、函數(shù)式編程庫等等概念......我們將再接再厲得使用它們,把代碼屎山進(jìn)行分類、打包、清理!讓它不斷美麗起來

更多關(guān)于compose優(yōu)化代碼可讀性擴(kuò)展性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論