2017-10-22 94 views
5

Używam Kotlin współprogram na zamówienie sieci przy użyciu metody rozszerzenie zadzwonić klasę w modernizowanych jak tenJak wykładniczy wycofywania ponownie na Kotlin współprogram

public suspend fun <T : Any> Call<T>.await(): T { 

    return suspendCancellableCoroutine { continuation -> 

    enqueue(object : Callback<T> { 

     override fun onResponse(call: Call<T>?, response: Response<T?>) { 
      if (response.isSuccessful) { 
       val body = response.body() 
       if (body == null) { 
        continuation.resumeWithException(
          NullPointerException("Response body is null") 
        ) 
       } else { 
        continuation.resume(body) 
       } 
      } else { 
       continuation.resumeWithException(HttpException(response)) 
      } 
     } 

     override fun onFailure(call: Call<T>, t: Throwable) { 
      // Don't bother with resuming the continuation if it is already cancelled. 
      if (continuation.isCancelled) return 
      continuation.resumeWithException(t) 
     } 
    }) 

     registerOnCompletion(continuation) 
    } 
} 

następnie z wywołaniem strony używam powyżej sposobu jak ten

private fun getArticles() = launch(UI) { 

    loading.value = true 
    try { 
     val networkResult = api.getArticle().await() 
     articles.value = networkResult 

    }catch (e: Throwable){ 
     e.printStackTrace() 
     message.value = e.message 

    }finally { 
     loading.value = false 
    } 

} 

Chcę, aby wykładniczy ponowić wezwanie api w niektórych przypadkach, tj. (Wyjątek IOEception), w jaki sposób mogę to osiągnąć?

Odpowiedz

22

Proponuję napisać pomocnika higher-order function dla logiki ponownej próby. Można użyć następującego realizacji na początek:

suspend fun <T> retryIO(
    times: Int = Int.MAX_VALUE, 
    initialDelay: Long = 100, // 0.1 second 
    maxDelay: Long = 1000, // 1 second 
    factor: Double = 2.0, 
    block: suspend() -> T): T 
{ 
    var currentDelay = initialDelay 
    repeat(times - 1) { 
     try { 
      return block() 
     } catch (e: IOException) { 
      // you can log an error here and/or make a more finer-grained 
      // analysis of the cause to see if retry is needed 
     } 
     delay(currentDelay) 
     currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) 
    } 
    return block() // last attempt 
} 

Korzystanie z tej funkcji jest bardzo strightforward:

val networkResult = retryIO { api.getArticle().await() } 

można zmieniać parametry ponawiania na podstawie indywidualnych przypadków, na przykład:

val networkResult = retryIO(times = 3) { api.doSomething().await() } 

Możesz również całkowicie zmienić implementację retryIO, aby dopasować ją do potrzeb Twojej aplikacji. Na przykład możesz zakodować wszystkie parametry ponownej próby, pozbyć się limitu liczby ponownych prób, zmienić ustawienia domyślne, itp.

+0

wow bardzo fajna odpowiedź dzięki –

+1

To było coś, co znajdowało się z tyłu mojej głowy dla już kilka dni. Cieszę się, że rozwiązanie nie jest bardziej skomplikowane niż to, co sobie wyobrażałem. Zadaję sobie także pytanie, czy sensowne byłoby zdefiniowanie tej funkcji pomocniczej jako funkcji inline. I na koniec, co nie mniej ważne: w jaki sposób zmodyfikować a, jeśli chcesz wykonać ponowienie tylko po zapytaniu użytkownika (np. W dialogu)? –

+1

Również o wiele czystsze niż rozwiązanie Rx: -O – kenyee