一個有味道的函數(shù)

最近想到了一個自認為很有意思的面試題
如何實現(xiàn)一個compose函數(shù)摔敛。
函數(shù)接收數(shù)個參數(shù)廷蓉,參數(shù)均為Function類型,右側函數(shù)的執(zhí)行結果將作為左側函數(shù)執(zhí)行的參數(shù)來調(diào)用舷夺。

compose(arg => `${arg}%`, arg => arg.toFixed(2), arg => arg + 10)(5) // 15.00%
compose(arg => arg.toFixed(2), arg => arg + 10)(5) // 15.00
compose(arg => arg + 10)(5) // 15

執(zhí)行結果如上述代碼苦酱,有興趣的同學可以先自己實現(xiàn)一下再來看后續(xù)的。

1.0實現(xiàn)方案

大致的思路為:

  1. 獲取所有的參數(shù)
  2. 調(diào)用最后一個函數(shù)给猾,并接收返回值
  3. 如果沒有后續(xù)的函數(shù)疫萤,返回數(shù)據(jù),如果有敢伸,將返回值放入下一個函數(shù)中執(zhí)行

所以這種情況用遞歸來實現(xiàn)會比較清晰一些

function compose (...funcs) {
  return function exec (arg) {
    let func = funcs.pop()
    let result = func(arg) // 執(zhí)行函數(shù)扯饶,獲取返回值

    // 如果后續(xù)還有函數(shù),將返回值放入下一個函數(shù)執(zhí)行
    // 如果后續(xù)沒有了池颈,直接返回
    return funcs.length ? exec(result) : result
  }
}

這樣尾序,我們就實現(xiàn)了上述的compose函數(shù)。
真是可喜可賀躯砰,可喜可賀每币。

本文完。




好了琢歇,如果現(xiàn)實生活中開發(fā)做需求也是如此爽快不做作就好了兰怠,但是梦鉴,產(chǎn)品總是會來的,需求總是會改的揭保。

2.0需求變更

我們現(xiàn)在有如下要求肥橙,函數(shù)需要支持Promise對象,而且要兼容普通函數(shù)的方式秸侣。
示例代碼如下:

// 為方便閱讀修改的排版
compose(
  arg => new Promise((resolve, reject) =>
    setTimeout(_ =>
      resolve(arg.toFixed(2)),
      1000
    )
  ),
  arg => arg + 10
)(5).then(data => {
  console.log(data) // 15.00
})

我們有如下代碼調(diào)用存筏,對toFixed函數(shù)的調(diào)用添加1000ms的延遲。讓用戶覺得這個函數(shù)執(zhí)行很慢味榛,方便下次優(yōu)化

所以椭坚,我們就需要去修改compose函數(shù)了。
我們之前的代碼只能支持普通函數(shù)的處理励负,現(xiàn)在因為添加了Promise對象的原因藕溅,所以我們要進行如下修改:

首先匕得,異步函數(shù)改為同步函數(shù)是不存在的readFile/readFileSync這類除外继榆。
所以,最簡單的方式就是汁掠,我們將普通函數(shù)改為異步函數(shù)略吨,也就是在普通函數(shù)外包一層Promise

function compose (...funcs) {
  return function exec (arg) {
    return new Promise((resolve, reject) => {
      let func = funcs.pop()

      let result = promiseify(func(arg)) // 執(zhí)行函數(shù)考阱,獲取返回值翠忠,并將返回值轉換為`Promise`對象

      // 注冊`Promise`的`then`事件,并在里邊進行下一次函數(shù)執(zhí)行的準備
      // 判斷后續(xù)是否還存在函數(shù)乞榨,如果有秽之,繼續(xù)執(zhí)行
      // 如果沒有,直接返回結果
      result.then(data => funcs.length ?
        exec(data).then(resolve).catch(reject) :
        resolve(data)
      ).catch(reject)
    })
  }
}

// 判斷參數(shù)是否為`Promise`
function isPromise (pro) {
  return pro instanceof Promise
}

// 將參數(shù)轉換為`Promise`
function promiseify (pro) {
  // 如果結果為`Promise`吃既,直接返回
  if (isPromise(pro)) return pro
  // 如果結果為這些基本類型考榨,說明是普通函數(shù)
  // 我們給他包一層`Promise.resolve`
  if (['string', 'number', 'regexp', 'object'].includes(typeof pro)) return Promise.resolve(pro)
}

我們針對compose代碼的改動主要是集中在這幾處:

  1. compose的返回值改為了Promise對象,這個是必然的鹦倚,因為內(nèi)部可能會包含Promise參數(shù)河质,所以我們一定要返回一個Promise對象
  2. 將各個函數(shù)執(zhí)行的返回值包裝為了Promise對象,為了統(tǒng)一返回值震叙。
  3. 處理函數(shù)返回值掀鹅,監(jiān)聽thencatch、并將resolvereject傳遞了過去媒楼。

3.0終極版

現(xiàn)在乐尊,我們又得到了一個新的需求,我們想要在其中某些函數(shù)執(zhí)行中跳過部分代碼划址,先執(zhí)行后續(xù)的函數(shù)扔嵌,等到后續(xù)函數(shù)執(zhí)行完后昏滴,再拿到返回值執(zhí)行剩余的代碼:

compose(
  data => new Promise((resolve, reject) => resolve(data + 2.5)),
  data => new Promise((resolve, reject) => resolve(data + 2.5)),
  async function c (data, next) { // async/await為Promise語法糖,不贅述
    data += 10 // 數(shù)值 + 10
    let result = await next(data) // 先執(zhí)行后續(xù)的代碼

    result -= 5  // 數(shù)值 - 5

    return result
  },
  (data, next) => new Promise((resolve, reject) => {
    next(data).then(data => {
      data = data / 100 // 將數(shù)值除以100限制百分比
      resolve(`${data}%`)
    }).catch(reject) // 先執(zhí)行后續(xù)的代碼
  }),
  function d (data) { return data + 20 }
)(15).then(console.log) // 0.45%

拿到需求后对人,陷入沉思谣殊。。牺弄。
好好地順序執(zhí)行代碼姻几,突然就變成了這個鳥樣,隨時可能會跳到后邊的函數(shù)去势告。
所以我們分析這個新需求的效果:

我們在函數(shù)執(zhí)行到一半時蛇捌,執(zhí)行了nextnext的返回值為后續(xù)函數(shù)的執(zhí)行返回值咱台。
也就是說络拌,我們在next中處理,直接調(diào)用隊列中的下一個函數(shù)即可回溺;
然后監(jiān)聽thencatch回調(diào)春贸,即可在當前函數(shù)中獲取到返回值;
拿到返回值后就可以執(zhí)行我們后續(xù)的代碼遗遵。

然后他的實現(xiàn)呢萍恕,也是非常的簡單,我們只需要修改如下代碼即可完成操作:

// 在這里會強行調(diào)用`exec`并傳入?yún)?shù)
// 而`exec`的執(zhí)行车要,則意味著`funcs`集合中又一個函數(shù)被從隊列中取出來
promiseify(func(arg, arg => exec(arg)))

也就是說允粤,我們會提前執(zhí)行下一個函數(shù),而且下一個函數(shù)的then事件注冊是在我們當前函數(shù)內(nèi)部的翼岁,當我們拿到返回值后类垫,就可以進行后續(xù)的處理了。
而我們所有的函數(shù)是存放在一個隊列里的琅坡,在我們提前執(zhí)行完畢該函數(shù)后悉患,后續(xù)的執(zhí)行也就不會再出現(xiàn)了。避免了一個函數(shù)被重復執(zhí)行的問題脑蠕。

如果看到這里已經(jīng)很明白了购撼,那么恭喜,你已經(jīng)了解了實現(xiàn)koajs最核心的代碼:
中間件的實現(xiàn)方式谴仙、洋蔥模型


想必現(xiàn)在整個函數(shù)周遭散發(fā)著洋蔥的味道迂求。

參考資料

koa-compose

相關示例代碼倉庫

1.0,普通函數(shù)
2.0晃跺,Promise函數(shù)
3.0揩局,支持洋蔥模型

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掀虎,隨后出現(xiàn)的幾起案子凌盯,更是在濱河造成了極大的恐慌付枫,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驰怎,死亡現(xiàn)場離奇詭異阐滩,居然都是意外死亡,警方通過查閱死者的電腦和手機县忌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門掂榔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人症杏,你說我怎么就攤上這事装获。” “怎么了厉颤?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵穴豫,是天一觀的道長。 經(jīng)常有香客問我逼友,道長精肃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任翁逞,我火速辦了婚禮肋杖,結果婚禮上溉仑,老公的妹妹穿的比我還像新娘挖函。我一直安慰自己,他們只是感情好浊竟,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布怨喘。 她就那樣靜靜地躺著,像睡著了一般振定。 火紅的嫁衣襯著肌膚如雪必怜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天后频,我揣著相機與錄音梳庆,去河邊找鬼。 笑死卑惜,一個胖子當著我的面吹牛膏执,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播露久,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼更米,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毫痕?” 一聲冷哼從身側響起征峦,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤迟几,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后栏笆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體类腮,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年蛉加,在試婚紗的時候發(fā)現(xiàn)自己被綠了存哲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡七婴,死狀恐怖祟偷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情打厘,我是刑警寧澤修肠,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站户盯,受9級特大地震影響嵌施,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莽鸭,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一吗伤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硫眨,春花似錦足淆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姥闭,卻和暖如春丹鸿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棚品。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工靠欢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铜跑。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓门怪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疼进。 傳聞我的和親對象是個殘疾皇子薪缆,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355