摘要:所以我們分析這個新需求的效果我們在函數(shù)執(zhí)行到一半時,執(zhí)行了,的返回值為后續(xù)函數(shù)的執(zhí)行返回值。也就是說,我們在中處理,直接調(diào)用隊列中的下一個函數(shù)即可然后監(jiān)聽和回調(diào),即可在當前函數(shù)中獲取到返回值拿到返回值后就可以執(zhí)行我們后續(xù)的代碼。
最近想到了一個自認為很有意思的面試題
如何實現(xiàn)一個compose函數(shù)。
函數(shù)接收數(shù)個參數(shù),參數(shù)均為Function類型,右側(cè)函數(shù)的執(zhí)行結(jié)果將作為左側(cè)函數(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í)行結(jié)果如上述代碼,有興趣的同學可以先自己實現(xiàn)一下再來看后續(xù)的。
1.0實現(xiàn)方案大致的思路為:
獲取所有的參數(shù)
調(diào)用最后一個函數(shù),并接收返回值
如果沒有后續(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ù),獲取返回值,并將返回值轉(zhuǎn)換為`Promise`對象 // 注冊`Promise`的`then`事件,并在里邊進行下一次函數(shù)執(zhí)行的準備 // 判斷后續(xù)是否還存在函數(shù),如果有,繼續(xù)執(zhí)行 // 如果沒有,直接返回結(jié)果 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ù)轉(zhuǎn)換為`Promise` function promiseify (pro) { // 如果結(jié)果為`Promise`,直接返回 if (isPromise(pro)) return pro // 如果結(jié)果為這些基本類型,說明是普通函數(shù) // 我們給他包一層`Promise.resolve` if (["string", "number", "regexp", "object"].includes(typeof pro)) return Promise.resolve(pro) }
我們針對compose代碼的改動主要是集中在這幾處:
將compose的返回值改為了Promise對象,這個是必然的,因為內(nèi)部可能會包含Promise參數(shù),所以我們一定要返回一個Promise對象
將各個函數(shù)執(zhí)行的返回值包裝為了Promise對象,為了統(tǒng)一返回值。
處理函數(shù)返回值,監(jiān)聽then和catch、并將resolve和reject傳遞了過去。
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í)行了next,next的返回值為后續(xù)函數(shù)的執(zhí)行返回值。
也就是說,我們在next中處理,直接調(diào)用隊列中的下一個函數(shù)即可;
然后監(jiān)聽then和catch回調(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ù)被重復(fù)執(zhí)行的問題。
如果看到這里已經(jīng)很明白了,那么恭喜,你已經(jīng)了解了實現(xiàn)koajs最核心的代碼:
中間件的實現(xiàn)方式、洋蔥模型
想必現(xiàn)在整個函數(shù)周遭散發(fā)著洋蔥的味道。
參考資料koa-compose
相關(guān)示例代碼倉庫1.0,普通函數(shù)
2.0,Promise函數(shù)
3.0,支持洋蔥模型
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93790.html
摘要:壞的味道指的是應(yīng)該被修改,被重構(gòu)的代碼,不具有可讀性,復(fù)用性,判斷邏輯復(fù)雜,冗余代碼。它們通常能指出代碼用途和實現(xiàn)手法之間的語義距離。把所有和這個變量相關(guān)的代碼新建一個類放入。但這往往不夠,請反復(fù)運用將某些行為移入類,直到者的協(xié)議一致為止。 壞的味道:指的是應(yīng)該被修改,被重構(gòu)的代碼,不具有可讀性,復(fù)用性,判斷邏輯復(fù)雜,冗余代碼。應(yīng)該使用各種重構(gòu)的手法去改變它! Duplicated...
摘要:壞味道的代碼重復(fù)代碼會自動標注重復(fù)的代碼。一般都是遇到真實情況后才考慮得到霰彈式修改添加或修改一個功能引發(fā)多個類相應(yīng)修改遇到這種情況可以移動代碼,將需要修改的代碼都放在同一個類下。被拒絕的遺贈子類應(yīng)該繼承超類的函數(shù)和數(shù)據(jù)。 壞味道的代碼 重復(fù)代碼 idea會自動標注重復(fù)的代碼。一般重復(fù)代碼就是可以重構(gòu)的點。 同一個類的兩個函數(shù)還有相同的表達式,這時需要提煉出重復(fù)代碼。 兩個互為兄弟的...
摘要:無論如何,單元測試一直是一中非常重要卻常常被忽視的技能。在實踐中,重構(gòu)的要求是很高的它需要有足夠詳盡的單元測試,需要有持續(xù)集成的環(huán)境,需要隨時隨地在小步伐地永遠讓代碼處于可工作狀態(tài)下去進行改善。 showImg(https://segmentfault.com/img/bVbttWF?w=1000&h=528); 五月初的時候朋友和我說《重構(gòu)》出第 2 版了,我才興沖沖地下單,花了一個...
摘要:前端基本功示例代碼一點這里前端基本功示例代碼二點這里一像素偽類實現(xiàn)對于老項目,有沒有什么辦法能兼容的尷尬問題了,個人認為偽類是比較完美的方法了。 前端基本功-示例代碼 (一) 點這里前端基本功-示例代碼 (二) 點這里 1.一像素 偽類 + transform 實現(xiàn)對于老項目,有沒有什么辦法能兼容1px的尷尬問題了,個人認為偽類+transform是比較完美的方法了。 原理是把原先元素...
閱讀 2833·2021-11-25 09:43
閱讀 2477·2021-10-09 09:44
閱讀 2801·2021-09-22 15:49
閱讀 2568·2021-09-01 11:43
閱讀 2542·2019-08-30 14:16
閱讀 465·2019-08-29 17:24
閱讀 3020·2019-08-29 14:00
閱讀 1384·2019-08-29 13:05