【譯】對RxJava中.repeatWhen()和.retryWhen()操作符的思考

第一次見到.repeatWhen().retryWhen()這兩個操作符的時(shí)候就非常困惑了童番。不得不說精钮,它們絕對是“最令人困惑彈珠圖”的有力角逐者。

然而它們都是非常有用的操作符:允許你有條件的重新訂閱已經(jīng)結(jié)束的Observable剃斧。我最近研究了它們的工作原理轨香,現(xiàn)在我希望嘗試著去解釋它們(因?yàn)椋乙彩呛馁M(fèi)了一些精力才參透它們)幼东。

Repeat與Retry的對比

首先臂容,來了解一下.repeat().retry()之間最直觀的區(qū)別是什么?這個問題并不難:區(qū)別就在于什么樣的終止事件會觸發(fā)重訂閱根蟹。

當(dāng).repeat()接收到.onCompleted()事件后觸發(fā)重訂閱脓杉。

當(dāng).retry()接收到.onError()事件后觸發(fā)重訂閱。

然而简逮,這種簡單的敘述尚不能令人滿意球散。試想如果你要實(shí)現(xiàn)一個延遲數(shù)秒的重訂閱該如何去做?或者想通過觀察錯誤來決定是否應(yīng)該重訂閱呢散庶?這種情況下就需要.repeatWhen().retryWhen()的介入了蕉堰,因?yàn)樗鼈冊试S你為重試提供自定義邏輯凌净。

Notification Handler

你可以通過一個叫做notificationHandler的函數(shù)來實(shí)現(xiàn)重試邏輯。這是.retryWhen()的方法簽名(譯者注:方法簽名屋讶,指方法名稱冰寻、參數(shù)類型和參數(shù)數(shù)量等):

retryWhen(Func1<? super Observable<? extends java.lang.Throwable>,? extends Observable<?>> notificationHandler) 

簽名很長,甚至不能一口氣讀完皿渗。我發(fā)現(xiàn)它很難理解的原因是因?yàn)榇嬖谝淮蠖训姆盒图s定性雄。

簡化后,它包括三個部分:

  1. Func1像個工廠類羹奉,用來實(shí)現(xiàn)你自己的重試邏輯秒旋。
  2. 輸入的是一個Observable<Throwable>
  3. 輸出的是一個Observable<?>诀拭。

首先迁筛,讓我們來看一下最后一部分。被返回的Observable<?>所要發(fā)送的事件決定了重訂閱是否會發(fā)生耕挨。如果發(fā)送的是onCompleted或者onError事件细卧,將不會觸發(fā)重訂閱。相對的筒占,如果它發(fā)送onNext事件贪庙,則觸發(fā)重訂閱(不管onNext實(shí)際上是什么事件)。這就是為什么使用了通配符作為泛型類型:這僅僅是個通知(next, error或者completed)翰苫,一個很重要的通知而已止邮。

source每次一調(diào)用onError(Throwable)Observable<Throwable>都會被作為輸入傳入方法中奏窑。換句話說就是导披,它的每一次調(diào)用你都需要決定是否需要重訂閱。

當(dāng)訂閱發(fā)生的時(shí)候埃唯,工廠Func1被調(diào)用撩匕,從而準(zhǔn)備重試邏輯。那樣的話墨叛,當(dāng)onError被調(diào)用后止毕,你已經(jīng)定義的重試邏輯就能夠處理它了。

這里有個例子展示了我們應(yīng)該在哪些場景下訂閱source漠趁,比如扁凛,只有在ThrowableIOException的情況下請求重訂閱,否則不(重訂閱)棚潦。

source.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
          @Override public Observable<?> call(Observable<? extends Throwable> errors) {

            return errors.flatMap(new Func1<Throwable, Observable<?>>() {
              @Override public Observable<?> call(Throwable error) {

                // For IOExceptions, we  retry
                if (error instanceof IOException) {
                  return Observable.just(null);
                }

                // For anything else, don't retry
                return Observable.error(error);
              }
            });
          }  
        })

由于每一個error都被flatmap過令漂,因此我們不能通過直接調(diào)用.onNext(null)觸發(fā)重訂閱或者.onError(error)來避免重訂閱。

經(jīng)驗(yàn)之談

這里有一些關(guān)于.repeatWhen().retryWhen()的要點(diǎn)丸边,我們應(yīng)該牢記于心叠必。

  • .repeatWhen().retryWhen()非常相似,只不過不再響應(yīng)onError作為重試條件妹窖,而是onCompleted纬朝。因?yàn)?code>onCompleted沒有類型,所有輸入變?yōu)?code>Observable<Void>骄呼。

  • 每一次事件流的訂閱notificationHandler(也就是Func1)只會調(diào)用一次共苛。這也是講得通的,因?yàn)槟阌幸粋€可觀測的Observable<Throwable>蜓萄,它能夠發(fā)送任意數(shù)量的error隅茎。

  • 輸入的Observable必須作為輸出Observable的源。你必須對Observable<Throwable>做出反應(yīng)嫉沽,然后基于它發(fā)送事件辟犀;你不能只返回一個通用泛型流。

換言之就是绸硕,你不能做類似的操作:

 .retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Throwable> errors) {

                return Observable.just(null);}
            })

因?yàn)樗粌H不能奏效堂竟,而且還會打斷你的鏈?zhǔn)浇Y(jié)構(gòu)。你應(yīng)該做的是玻佩,而且至少應(yīng)該做的是出嘹,把輸入作為結(jié)果返回,就像這樣:

.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Throwable> errors) {

                return errors;
              }
            })

(順便提一下咬崔,這在邏輯上與單純使用.retry()操作符的效果是一樣噠)

  • 輸入Observable只在終止事件發(fā)生的時(shí)候才會觸發(fā)(對于.repeatWhen()來說是onCompleted,而對于.retryWhen()來說是onError)税稼。它不會從源中接收到任何onNext的通知,所以你不能通過觀察被發(fā)送的事件來決定重訂閱垮斯。如果你真的需要這樣做娶聘,你應(yīng)該添加像.takeUntil()這樣的操作符,來攔截事件流甚脉。

使用方式

現(xiàn)在丸升,假設(shè)你已大概了解了.repeatWhen().retryWhen(),那么你能將一些什么樣的精簡邏輯放入到notificationHandler中呢牺氨?

使用.repeatWhen() + .delay()定期輪詢數(shù)據(jù):

source.repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Void> completed) {

                return completed.delay(5, TimeUnit.SECONDS);
              }
            })

直到notificationHandler發(fā)送onNext()才會重訂閱到source狡耻。因?yàn)樵诎l(fā)送onNext()之前delay了一段時(shí)間,所以優(yōu)雅的實(shí)現(xiàn)了延遲重訂閱猴凹,從而避免了不間斷的數(shù)據(jù)輪詢夷狰。

非此即彼,使用.flatMap() + .timer()實(shí)現(xiàn)延遲重訂閱:
(譯者注:在RxJava 1.0.0及其之后的版本郊霎,官方已不再提倡使用.timer()操作符沼头,因?yàn)?code>.interval()具有同樣的功能)

source.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Throwable> errors) {

                return errors.flatMap(new Func1<Throwable, Observable<?>>() {
                  @Override public Observable<?> call(Throwable error) {

                    return Observable.timer(5, TimeUnit.SECONDS);
                  }
                });
              }
            })

當(dāng)需要與其他邏輯協(xié)同的時(shí)候,這種替代方案就變得非常有用了,比如进倍。土至。。

使用.zip() + .range()實(shí)現(xiàn)有限次數(shù)的重訂閱

source.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Throwable> errors) {

                return errors.zipWith(Observable.range(1, 3), new Func2<Throwable, Integer, Integer>() {
                  @Override public Integer call(Throwable throwable, Integer i) {

                    return i;
                  }
                });
              }
            })

最后的結(jié)果就是每個error都與range中一個輸出配對出現(xiàn)猾昆,就像這樣:

zip(error1, 1) -> onNext(1)  <-- Resubscribe  
zip(error2, 2) -> onNext(2)  <-- Resubscribe  
zip(error3, 3) -> onNext(3)  <-- Resubscribe  
onCompleted()                <-- No resubscription  

因?yàn)楫?dāng)?shù)谒拇蝒rror出現(xiàn)的時(shí)候陶因,range(1,3)中的數(shù)字已經(jīng)耗盡了,所以它隱式調(diào)用了onCompleted()垂蜗,從而導(dǎo)致整個zip的結(jié)束楷扬。防止了進(jìn)一步的重試。

將可變延遲策略與次數(shù)限制的重試機(jī)制結(jié)合起來

source.retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
              @Override public Observable<?> call(Observable<? extends Throwable> errors) {

                return errors.zipWith(Observable.range(1, 3), new Func2<Throwable, Integer, Integer>() {
                  @Override public Integer call(Throwable throwable, Integer i) {

                    return i;
                  }
                }).flatMap(new Func1<Integer, Observable<? extends Long>>() {
                  @Override public Observable<? extends Long> call(Integer retryCount) {
                    
                    return Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS);
                  }
                });
              }
            })

在這種用例的比較上贴见,我認(rèn)為.flatMap()+.timer()的組合比單純使用.delay()更可取烘苹,因?yàn)槲覀兛梢酝ㄟ^重試次數(shù)來修改延遲時(shí)間。重試三次片部,并且每一次的重試時(shí)間都是5 ^ retryCount镣衡,僅僅通過一些操作符的組合就幫助我們實(shí)現(xiàn)了指數(shù)退避算法(譯者注:可參考二進(jìn)制指數(shù)退避算法)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吞琐,一起剝皮案震驚了整個濱河市捆探,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌站粟,老刑警劉巖黍图,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奴烙,居然都是意外死亡助被,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門切诀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揩环,“玉大人,你說我怎么就攤上這事幅虑》峄” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵倒庵,是天一觀的道長褒墨。 經(jīng)常有香客問我,道長擎宝,這世上最難降的妖魔是什么郁妈? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮绍申,結(jié)果婚禮上噩咪,老公的妹妹穿的比我還像新娘。我一直安慰自己胃碾,他們只是感情好涨享,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布书在。 她就那樣靜靜地躺著拆又,像睡著了一般儒旬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帖族,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音竖般,去河邊找鬼。 笑死涣雕,一個胖子當(dāng)著我的面吹牛艰亮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挣郭,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼迄埃,長吁一口氣:“原來是場噩夢啊……” “哼兑障!你這毒婦竟也來了侄非?” 一聲冷哼從身側(cè)響起流译,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叠赦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體除秀,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡业岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笔时。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡借笙,死狀恐怖扒怖,靈堂內(nèi)的尸體忽然破棺而出业稼,到底是詐尸還是另有隱情,我是刑警寧澤低散,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站熔号,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏朦蕴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一吩抓、第九天 我趴在偏房一處隱蔽的房頂上張望掉分。 院中可真熱鬧传趾,春花似錦、人聲如沸衙传。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽除师。三九已至,卻和暖如春汛聚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倚舀。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留风罩,地道東北人舵稠。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓入宦,卻偏偏與公主長得像室琢,于是被迫代替她去往敵國和親乾闰。 傳聞我的和親對象是個殘疾皇子盈滴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 注:只包含標(biāo)準(zhǔn)包中的操作符,用于個人學(xué)習(xí)及備忘參考博客:http://blog.csdn.net/maplejaw...
    小白要超神閱讀 911評論 0 3
  • 本篇文章介主要紹RxJava中操作符是以函數(shù)作為基本單位病苗,與響應(yīng)式編程作為結(jié)合使用的竿报,對什么是操作继谚、操作符都有哪些...
    嘎啦果安卓獸閱讀 2,837評論 0 10
  • 我從去年開始使用 RxJava ,到現(xiàn)在一年多了花履。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy閱讀 5,456評論 7 62
  • 作者: maplejaw本篇只解析標(biāo)準(zhǔn)包中的操作符济瓢。對于擴(kuò)展包,由于使用率較低旺矾,如有需求,請讀者自行查閱文檔箕宙。 創(chuàng)...
    maplejaw_閱讀 45,600評論 8 93
  • 文章轉(zhuǎn)自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物線在正...
    xpengb閱讀 7,019評論 9 73