return两个返回值_异步函数的两个视角
以下示例代碼是用Scala寫的,不過本文所講的話題并不僅限于Scala,任何有Future/Promise支持的語言都是適用的。
下面這個(gè)wiki頁面羅列了各個(gè)有Future/Promise支持的語言,已經(jīng)涵蓋了大多數(shù)的常用語言。
Future與promise實(shí)現(xiàn)列表
我是異步函數(shù)的編寫者
我寫了兩個(gè)異步函數(shù),來提供給其他程序員同事使用。
type CallBack = Try[String] => Unitdef pretendCallAPI(callBack: CallBack, okMsg: String, failedMsg: String) = {val task = new TimerTask {override def run() = {val percentage = Random.between(1, 100)if (percentage >= 50)callBack(Success(okMsg))else if (percentage <= 30)callBack(Failure(new Exception(failedMsg)))elsecallBack(Failure(new Exception("network problem")))}}new Timer().schedule(task, Random.between(200, 500)) }val searchTB = pretendCallAPI(_, "product price found", "product not listed") val buyFromTB = pretendCallAPI(_, "product bought", "can not buy, no money left")這兩個(gè)異步函數(shù): searchTB用來從淘寶搜索物品,另一個(gè)buyFromTB用來購買搜到的物品。
由于僅僅是為了演示而寫的,他們兩個(gè)都是基于一個(gè)叫做pretendCallAPI的函數(shù)實(shí)現(xiàn)的。
顧名思義,pretendCallAPI并不會(huì)真的去調(diào)用淘寶的API,而只是模擬API的行為。
這個(gè)pretendCallAPI函數(shù)有幾個(gè)行為特征:
- 每次耗時(shí)200到500毫秒之間
- 每次執(zhí)行有50%的幾率成功
- 20%的幾率遇到網(wǎng)絡(luò)故障
- 另外30%的幾率雖然網(wǎng)絡(luò)沒問題但是服務(wù)器會(huì)給你一個(gè)非正常的結(jié)果
當(dāng)然,由于我寫的是異步算法,需要避免block caller thread。
所以當(dāng)你調(diào)用pretendCallAPI的時(shí)候,這個(gè)函數(shù)是瞬間立即返回的。
那么當(dāng)然我就無法在函數(shù)返回的時(shí)候return什么有用的東西給你了。
如果你想知道執(zhí)行的結(jié)果到底是啥,你需要傳給我一個(gè)CallBack,在我執(zhí)行完后,通過CallBack來告知你執(zhí)行的結(jié)果。
這個(gè)CallBack的完整簽名表達(dá)式展開是Try[String] => Unit
大家看searchTB和buyFromTB可能覺得他們長的有點(diǎn)奇怪,這是Scala里柯里化的寫法。
也就是通過把pretendCallAPI包一層來構(gòu)造新的函數(shù),鎖死兩個(gè)參數(shù),剩下的一個(gè)參數(shù)(也就是CallBack)就變成了新構(gòu)造出來的函數(shù)的唯一參數(shù)了。
也就是說searchTB和buyFromTB的簽名是(Try[String] => Unit) => Unit。
關(guān)于柯里化這個(gè)語言特性的更多信息:
https://cuipengfei.me/blog/2013/12/25/desugar-scala-6/
好了,現(xiàn)在這兩個(gè)函數(shù)可以提供給大家使用了。
我是異步函數(shù)的調(diào)用者
聽說異步函數(shù)已經(jīng)寫好了,我終于可以用他們來實(shí)現(xiàn)剁手業(yè)務(wù)了。
聽函數(shù)作者講了一下,用起來應(yīng)該不會(huì)很難,那我來實(shí)現(xiàn)一下吧。
def searchPriceThenBuy() = {searchTB {case Success(searchMsg) =>println(searchMsg)buyFromTB {case Success(buyMsg) => println(buyMsg)case Failure(err) => println(err.getMessage)}case Failure(err) => println(err.getMessage)} }使用searchTB和buyFromTB并不難. 他們兩個(gè)都是接受CallBack作為參數(shù)的函數(shù)。
CallBack本身是個(gè)函數(shù),它的簽名是Try[String] => Unit。
而Try有兩種形式,分別是Success和Failure。
所以在調(diào)用searchTB和buyFromTB的時(shí)候,必須把兩個(gè)分支都給到(以免pattern match不到)。
這樣在異步函數(shù)有結(jié)果的時(shí)候(無論成敗)才能call back過來到我的代碼,以便我能夠在合適的時(shí)機(jī)做后續(xù)的處理(無論是基于成功做后續(xù)業(yè)務(wù),還是做error handling)。
關(guān)于pattern match,可以參考這里:
https://cuipengfei.me/blog/2013/12/29/desugar-scala-8/
https://cuipengfei.me/blog/2015/06/16/visitor-pattern-pattern-match/
這段代碼跑一下的話,會(huì)有這么幾種結(jié)果:
- 搜到了,也買到了
- 搜到了,購買時(shí)遇到了網(wǎng)絡(luò)故障
- 搜到了,由于支付寶錢不夠而沒買到
- 沒搜到,購買行為未觸發(fā)
- 搜索遇到網(wǎng)絡(luò)故障,購買行為未觸發(fā)
一共就這么幾種可能,因?yàn)閜retendCallAPI是跑概率的,多跑幾次這些情況都能遇到。
雖然實(shí)現(xiàn)出來不難,執(zhí)行結(jié)果也沒問題,但是總有點(diǎn)隱憂。
這里只有searchTB和buyFromTB兩個(gè)函數(shù),如果其他場(chǎng)景下我需要把更多的異步函數(shù)組合起來使用呢?豈不是要縮進(jìn)很多層?
當(dāng)然,縮進(jìn)只是個(gè)視覺審美問題,是個(gè)表象,不是特別要緊.關(guān)鍵是我的業(yè)務(wù)邏輯很容易被這樣的代碼給割裂的雞零狗碎,那就不好了。
我要給上游編寫異步函數(shù)的同事反饋一下,看是否有辦法解決這個(gè)問題。
鏡頭切回到異步函數(shù)編寫者
之前寫的兩個(gè)函數(shù)反饋不太好,主要是因?yàn)橥聜冋J(rèn)為使用CallBack不是最優(yōu)的方式。
這個(gè)反饋確實(shí)很中肯,如果只有一個(gè)異步函數(shù)單獨(dú)使用,用CallBack也沒什么太大的問題,如果是很多個(gè)異步函數(shù)組合使用確實(shí)會(huì)形成多層嵌套的問題。
我作為上游程序員,確實(shí)需要更多地為下游調(diào)用者考慮。
既然如此,那我改版一下,免除掉讓下游使用CallBack的必要性。
type CallBackBasedFunction = (CallBack) => Unitdef futurize(f: CallBackBasedFunction) = () => {val promise = Promise[String]()f {case Success(msg) => promise.success(msg)case Failure(err) => promise.failure(err)}promise.future }val searchTBFutureVersion = futurize(searchTB) val buyFromTBFutureVersion = futurize(buyFromTB)先定義一個(gè)CallBackBasedFunction,它代表一個(gè)接受CallBack為參數(shù)的函數(shù)的簽名。
表達(dá)式展開后就是: (Try[String] => Unit) => Unit
這就符合了searchTB和buyFromTB兩個(gè)函數(shù)的簽名。
futurize算是個(gè)higher order function,它接受一個(gè)CallBackBasedFunction作為參數(shù),返回一個(gè)() => Future[String]。
(Future是Scala標(biāo)準(zhǔn)庫的內(nèi)容,可以認(rèn)為和JS Promises/A+是類似的概念)
也就是說futurize可以把searchTB和buyFromTB改造成返回Future的函數(shù)。上面代碼最后兩行就是改造的結(jié)果。
這樣,原本接受CallBack做為參數(shù)且沒有返回值的函數(shù),就變成了不接受參數(shù)且返回Future的函數(shù)。
再看futurize的具體實(shí)現(xiàn),它使用了Scala的Promise,讓返回的Future在原版函數(shù)成功時(shí)成功,在原版函數(shù)失敗時(shí)失敗。
這樣,我就得到了searchTBFutureVersion和buyFromTBFutureVersion這兩個(gè)仍然是立即瞬間返回,不會(huì)block caller thread的函數(shù)。
關(guān)于Scala中Promise和Future的更多信息:
https://docs.scala-lang.org/overviews/core/futures.html
鏡頭再切到異步函數(shù)調(diào)用者
現(xiàn)在有了searchTBFutureVersion和buyFromTBFutureVersion,我來試著重新實(shí)現(xiàn)一次:
def searchPriceThenBuyFutureVersion() = {val eventualResult = for {searchResult <- searchTBFutureVersion().map(msg => println(msg))buyResult <- buyFromTBFutureVersion().map(msg => println(msg))} yield (searchResult, buyResult)eventualResult.onComplete {case Failure(err) => println(err.getMessage)case _ =>} }這里用到了Scala的for comprehension,編譯后會(huì)變成map,flatMap等等monadic operator。
而map,flatMap等操作符正是Scala中Future拿來做組合用的。
這樣,用for把兩個(gè)返回Future的異步函數(shù)組織起來,形成一個(gè)新的Future,然后在新的Future complete時(shí)統(tǒng)一處理異常。
關(guān)于for的更多信息:
https://cuipengfei.me/blog/2014/08/30/options-for/
這次實(shí)現(xiàn)的代碼與上次的行為是一致的,沒什么兩樣。
不過我的業(yè)務(wù)代碼從雞零狗碎變成了平鋪直敘平易近人。
(這種效果在這里表現(xiàn)的并不是特別突出,不過很容易想象如果需要組合使用的異步函數(shù)更多一些的話,這種效果的好處就顯露出來了)
當(dāng)然了,讓業(yè)務(wù)代碼易讀易懂主要還是要靠個(gè)人奮斗,而有了Promise和Future這種歷史進(jìn)程的推力,則更有增益作用。
小結(jié)
最近在看Scala Reactive的一些內(nèi)容
想起了很久之前寫過一篇叫做自己動(dòng)手實(shí)現(xiàn)Promises/A+規(guī)范的博客,用JS實(shí)現(xiàn)了一個(gè)簡版的Promise:
https://cuipengfei.me/blog/2016/05/15/promise/
我在當(dāng)時(shí)的一段演示代碼里面寫了兩句注釋:
Promise的作用在于
不過當(dāng)時(shí)的博客里只講了實(shí)現(xiàn)Promise規(guī)范的事情,并沒有詳細(xì)解釋過這兩句話。
既然又遇到了這個(gè)話題,于是寫點(diǎn)Scala來把當(dāng)時(shí)沒展開寫到的內(nèi)容補(bǔ)充了一下。
上文的四個(gè)鏡頭展現(xiàn)了兩個(gè)角色的思考過程,通過這個(gè)過程其實(shí)也就解釋了上面兩句注釋的含義。
1.給異步算法的編寫者和使用者之間提供一種統(tǒng)一的交流手段
所謂統(tǒng)一的交流手段,其實(shí)就是異步函數(shù)的簽名問題。
由于需要處理的業(yè)務(wù)五花八門,異步函數(shù)接受的參數(shù)列表沒法統(tǒng)一,但是返回值是可以統(tǒng)一的。
一個(gè)異步函數(shù),接受了外界給的參數(shù),立即瞬間返回一個(gè)Js的Promise或者Scala的Future(或者是任何語言中類似概念的叫法)。
然后在異步任務(wù)執(zhí)行完的時(shí)候把Promise resolve/reject掉(讓Future success或者failure),借此來讓調(diào)用方的代碼知道該到了它跑后續(xù)處理的時(shí)候了。
這樣我們就獲得了一個(gè)sensible default,無需在每次設(shè)計(jì)異步函數(shù)的時(shí)候都去商議該返回什么東西,該怎么獲得異步執(zhí)行的結(jié)果。
2.給異步算法的使用者提供一種組織代碼的手段,以便于將一層又一層嵌套的業(yè)務(wù)主流程變成一次一次的對(duì)then的調(diào)用
所謂組織代碼的手段,就是關(guān)于異步函數(shù)調(diào)用者的那兩個(gè)鏡頭的內(nèi)容了。
一開始CallBack套著CallBack,異步的味道很重,這體現(xiàn)出了代碼的組織方式在向代碼的技術(shù)實(shí)現(xiàn)低頭。或者說是代碼的技術(shù)實(shí)現(xiàn)干擾了我行文的風(fēng)格。
后來變成了看起來很像是消費(fèi)同步函數(shù)結(jié)果的寫法。從而讓我慣常的文風(fēng)得以保持。
文/ThoughtWorks 崔鵬飛
異步函數(shù)的兩個(gè)視角 - ThoughtWorks洞見?insights.thoughtworks.cn總結(jié)
以上是生活随笔為你收集整理的return两个返回值_异步函数的两个视角的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何ping端口_干货 | 如何正确处理
- 下一篇: tensorflow gpu安装_ten