KNETBatchProcessor pozwala na efektywne wykonywanie wielu requestów
równolegle z pełną kontrolą nad concurrency, progress tracking i obsługą błędów.
import rip.nerd.kitsunenet.util.KNETBatchProcessor
// Utwórz procesor z max 5 równoczesnymi requestami
val processor = KNETBatchProcessor(maxConcurrent = 5)
// Przygotuj listę requestów
val requests = listOf(
KNETRequest.get("https://api.example.com/users/1"),
KNETRequest.get("https://api.example.com/users/2"),
KNETRequest.get("https://api.example.com/users/3"),
KNETRequest.get("https://api.example.com/users/4"),
KNETRequest.get("https://api.example.com/users/5")
)
// Wykonaj wszystkie
val summary = processor.executeAll(client, requests)
// Sprawdź wyniki
println("Sukces: ${summary.successCount}/${summary.totalRequests}")
println("Błędy: ${summary.errorCount}")
println("Czas: ${summary.totalDurationMs}ms")
Wynik wykonania batch zawiera pełne statystyki:
data class BatchSummary(
val results: List<BatchResult>, // Wszystkie wyniki
val totalRequests: Int, // Liczba requestów
val successCount: Int, // Udane
val errorCount: Int, // Błędy
val totalDurationMs: Long, // Całkowity czas
val averageDurationMs: Long // Średni czas per request
) {
val successRate: Double // % sukcesu
val successful: List<BatchResult> // Tylko udane
val failed: List<BatchResult> // Tylko błędne
}
data class BatchResult(
val request: KNETRequest, // Oryginalny request
val response: KNETResponse?, // Odpowiedź (null przy błędzie)
val error: Throwable?, // Błąd (null przy sukcesie)
val index: Int, // Indeks w batch
val durationMs: Long // Czas wykonania
) {
val isSuccess: Boolean // Czy sukces
val isError: Boolean // Czy błąd
}
executeAll()Wykonuje wszystkie requesty równolegle (z limitem concurrency).
suspend fun executeAll(
client: KNETClient,
requests: List<KNETRequest>,
onProgress: ProgressCallback? = null
): BatchSummary
val processor = KNETBatchProcessor(maxConcurrent = 3)
val summary = processor.executeAll(client, requests) { completed, total, result ->
// Callback po każdym ukończonym requeście
val percent = (completed * 100) / total
runOnUiThread {
progressBar.progress = percent
statusText.text = "Pobieranie: $completed/$total"
}
if (result.isSuccess) {
Log.d("Batch", "✅ ${result.request.url}")
} else {
Log.e("Batch", "❌ ${result.request.url}: ${result.error?.message}")
}
}
// Podsumowanie
showToast("Pobrano ${summary.successCount} z ${summary.totalRequests} elementów")
executeAllFailFast()Przerywa przy pierwszym błędzie.
suspend fun executeAllFailFast(
client: KNETClient,
requests: List<KNETRequest>
): BatchSummary
// Użyj fail-fast gdy wszystkie requesty muszą się powieść
val summary = processor.executeAllFailFast(client, requests)
if (summary.errorCount > 0) {
// Przerwano na pierwszym błędzie
val failedResult = summary.failed.first()
showError("Operacja przerwana: ${failedResult.error?.message}")
// Możesz zobaczyć ile udało się wykonać przed błędem
Log.d("Batch", "Wykonano ${summary.successCount} przed błędem")
} else {
// Wszystkie OK
processResults(summary.successful)
}
executeAllSuccessful()Zwraca tylko udane odpowiedzi.
suspend fun executeAllSuccessful(
client: KNETClient,
requests: List<KNETRequest>
): List<KNETResponse>
// Pobierz tylko udane odpowiedzi
val responses = processor.executeAllSuccessful(client, requests)
// Mapuj na obiekty
val users = responses.map { it.json<User>() }
Log.d("Batch", "Pobrano ${users.size} użytkowników")
executeAndMap()Wykonuje i mapuje wyniki na dowolny typ.
suspend fun <T> executeAndMap(
client: KNETClient,
requests: List<KNETRequest>,
mapper: (KNETResponse) -> T
): List<T?>
// Pobierz i od razu zmapuj na obiekty User
val users: List<User?> = processor.executeAndMap(client, requests) { response ->
response.json<User>()
}
// Filtruj null-e (błędne requesty)
val validUsers = users.filterNotNull()
Log.d("Batch", "Pobrano ${validUsers.size} użytkowników")
executeForEach()Generuje requesty z listy elementów.
suspend fun <T> executeForEach(
client: KNETClient,
items: List<T>,
requestBuilder: (T) -> KNETRequest
): BatchSummary
val userIds = listOf("user1", "user2", "user3", "user4", "user5")
val summary = processor.executeForEach(client, userIds) { userId ->
KNETRequest.get("https://api.example.com/users/$userId")
}
// Lub z bardziej złożoną logiką
val itemsToUpdate = listOf(
UpdateItem(id = 1, name = "New Name 1"),
UpdateItem(id = 2, name = "New Name 2")
)
val updateSummary = processor.executeForEach(client, itemsToUpdate) { item ->
KNETRequest(
url = "https://api.example.com/items/${item.id}",
method = "PUT",
data = mapOf("name" to item.name)
)
}
executeAllWithRetry()Wykonuje z retry per request.
suspend fun executeAllWithRetry(
client: KNETClient,
requests: List<KNETRequest>,
retries: Int = 3,
onProgress: ProgressCallback? = null
): BatchSummary
// Każdy request będzie ponawiany do 3 razy przy błędzie
val summary = processor.executeAllWithRetry(
client,
requests,
retries = 3
) { completed, total, result ->
Log.d("Batch", "Progress: $completed/$total")
}
// Nawet jeśli niektóre requesty początkowo zawiodą,
// retry może je uratować
Log.d("Batch", "Sukces: ${summary.successCount}/${summary.totalRequests}")
executeSequential()Wykonuje sekwencyjnie (jeden po drugim).
suspend fun executeSequential(
client: KNETClient,
requests: List<KNETRequest>,
onProgress: ProgressCallback? = null
): BatchSummary
// Użyj gdy kolejność jest ważna lub API wymaga sekwencyjności
val summary = processor.executeSequential(client, requests)
// Wyniki są w tej samej kolejności co requesty
summary.results.forEachIndexed { index, result ->
println("Request $index: ${if (result.isSuccess) "OK" else "FAIL"}")
}
executeGroupedByHost()Grupuje requesty według hosta i wykonuje.
suspend fun executeGroupedByHost(
client: KNETClient,
requests: List<KNETRequest>
): Map<String, BatchSummary>
val mixedRequests = listOf(
KNETRequest.get("https://api.github.com/users/1"),
KNETRequest.get("https://api.github.com/users/2"),
KNETRequest.get("https://api.twitter.com/users/1"),
KNETRequest.get("https://api.twitter.com/users/2")
)
val resultsByHost = processor.executeGroupedByHost(client, mixedRequests)
resultsByHost.forEach { (host, summary) ->
println("$host: ${summary.successCount}/${summary.totalRequests}")
}
// Output:
// api.github.com: 2/2
// api.twitter.com: 2/2
// Standardowy (5 równoległych)
val processor = KNETBatchProcessor.standard()
// Dla urządzeń mobilnych (3 równoległe)
val processor = KNETBatchProcessor.mobile()
// Wysokiej przepustowości (10 równoległych)
val processor = KNETBatchProcessor.highThroughput()
// Throttled - max X requestów na sekundę
val processor = KNETBatchProcessor.throttled(
maxConcurrent = 5,
requestsPerSecond = 10
)
// Sekwencyjny (1 na raz)
val processor = KNETBatchProcessor.sequential()
// Z domyślnym retry
val processor = KNETBatchProcessor.withRetry(retries = 3)
| Preset | Max Concurrent | Delay | Retry | Użycie |
|---|---|---|---|---|
standard() |
5 | 0 | 0 | Domyślny |
mobile() |
3 | 0 | 0 | Oszczędność baterii |
highThroughput() |
10 | 0 | 0 | WiFi, szybkie API |
throttled(5, 10) |
5 | 100ms | 0 | API z rate limit |
sequential() |
1 | 0 | 0 | Zachowanie kolejności |
withRetry(3) |
5 | 0 | 3 | Niestabilne API |
class UserRepository(private val client: KNETClient) {
private val processor = KNETBatchProcessor.mobile()
suspend fun fetchUsers(ids: List<String>): List<User> {
val requests = ids.map { id ->
KNETRequest.get("https://api.example.com/users/$id")
}
return processor.executeAndMap(client, requests) { response ->
response.json<User>()
}.filterNotNull()
}
suspend fun fetchUsersWithProgress(
ids: List<String>,
onProgress: (Int, Int) -> Unit
): List<User> {
val requests = ids.map { id ->
KNETRequest.get("https://api.example.com/users/$id")
}
val summary = processor.executeAll(client, requests) { completed, total, _ ->
onProgress(completed, total)
}
return summary.successful.map { it.response!!.json<User>() }
}
}
class SyncService(private val client: KNETClient) {
private val processor = KNETBatchProcessor(maxConcurrent = 3)
suspend fun syncAll(): SyncResult {
val endpoints = listOf(
"https://api.example.com/users",
"https://api.example.com/products",
"https://api.example.com/orders",
"https://api.example.com/settings"
)
val requests = endpoints.map { KNETRequest.get(it) }
val summary = processor.executeAll(client, requests)
return SyncResult(
usersSync = summary.results[0].isSuccess,
productsSync = summary.results[1].isSuccess,
ordersSync = summary.results[2].isSuccess,
settingsSync = summary.results[3].isSuccess,
totalTime = summary.totalDurationMs
)
}
}
class BatchUpdateViewModel : ViewModel() {
private val processor = KNETBatchProcessor.withRetry(2)
private val _progress = MutableStateFlow(0f)
val progress = _progress.asStateFlow()
private val _status = MutableStateFlow<BatchStatus>(BatchStatus.Idle)
val status = _status.asStateFlow()
fun updateItems(items: List<Item>) {
viewModelScope.launch {
_status.value = BatchStatus.InProgress
val requests = items.map { item ->
KNETRequest(
url = "https://api.example.com/items/${item.id}",
method = "PUT",
data = item.toMap()
)
}
val summary = processor.executeAll(client, requests) { completed, total, _ ->
_progress.value = completed.toFloat() / total
}
_status.value = if (summary.errorCount == 0) {
BatchStatus.Success(summary.successCount)
} else {
BatchStatus.PartialSuccess(
success = summary.successCount,
failed = summary.errorCount,
errors = summary.failed.map { it.error?.message ?: "Unknown" }
)
}
}
}
}
sealed class BatchStatus {
object Idle : BatchStatus()
object InProgress : BatchStatus()
data class Success(val count: Int) : BatchStatus()
data class PartialSuccess(
val success: Int,
val failed: Int,
val errors: List<String>
) : BatchStatus()
}
val summary = processor.executeAll(client, requests)
// Sprawdź czy są błędy
if (summary.errorCount > 0) {
// Pokaż błędy
summary.failed.forEach { result ->
Log.e("Batch", "Błąd dla ${result.request.url}: ${result.error?.message}")
// Możesz spróbować ponownie tylko błędne
val failedRequests = summary.failed.map { it.request }
val retrySummary = processor.executeAllWithRetry(client, failedRequests, retries = 3)
}
}
// Lub użyj when dla różnych scenariuszy
when {
summary.errorCount == 0 -> {
showSuccess("Wszystkie operacje zakończone!")
}
summary.successCount == 0 -> {
showError("Wszystkie operacje nie powiodły się")
}
else -> {
showWarning("${summary.successCount} OK, ${summary.errorCount} błędów")
}
}