Result Type — tipe-wadah yang merepresentasikan hasil operasi yang dapat berakhir dengan sukses (Success) atau kesalahan (Failure). Berbeda dengan exception, Result meneruskan kesalahan sebagai nilai biasa tanpa membuka stack, yang membuat penanganan kesalahan yang diharapkan lebih aman dan dapat dikomposisikan. Menurut Apple Swift Documentation (2026), Result<Success, Failure> di Swift memungkinkan menggabungkan rantai operasi dengan penanganan kesalahan otomatis melalui map dan flatMap, tanpa menghentikan eksekusi program.
Poin utama
Result Type adalah tipe generik yang mengenkapsulasi hasil eksekusi operasi yang dapat berakhir baik dengan sukses maupun dengan kesalahan. Berbeda dengan exception, di mana kesalahan mengganggu aliran eksekusi normal dan memerlukan pembukaan stack untuk menemukan catch, Result meneruskan kesalahan sebagai nilai biasa — pihak pemanggil selalu menerima objek dan memutuskan sendiri bagaimana menanganinya. Ini sangat berguna untuk kesalahan yang diharapkan: input tidak valid, aturan bisnis, penolakan server, di mana exception akan menjadi mekanisme yang terlalu berat.
Konsep Result berasal dari pemrograman fungsional, di mana tipe serupa disebut Either (atau Left/Right). Di Swift, Result menjadi tipe standar di Swift 5.0, di Kotlin Result<T> muncul di pustaka standar, di Dart 3.0 — Result<T> bawaan. Setiap implementasi menyediakan metode untuk bekerja dengan wadah: map (transformasi nilai sukses), flatMap (rantai fungsi Result), mapError (transformasi kesalahan), fold (penanganan kedua kasus). Result Type — batu penjuru penanganan kesalahan fungsional dalam pengembangan mobile, alternatif untuk try-catch untuk skenario yang diharapkan.
Keuntungan utama Result adalah kemampuan komposisi. Anda dapat menggabungkan beberapa operasi, yang masing-masing dapat mengembalikan kesalahan, dalam satu rantai tanpa try-catch bersarang. Jika operasi apa pun dalam rantai berakhir dengan Failure, seluruh rantai terputus dan mengembalikan Failure — tanpa satu pun kondisi if atau blok catch. Ini membuat kode menjadi linier dan mudah dibaca, terutama dalam skenario dengan beberapa permintaan API berurutan atau pemeriksaan aturan bisnis.
Di Swift Result<Success, Failure> adalah enum dengan dua kasus: .success(Success) dan .failure(Failure), di mana Failure dibatasi oleh protokol Error. Swift Result adalah tipe fungsional lengkap dengan metode map, flatMap, mapError dan get. get adalah metode khusus: ia mengembalikan nilai Success jika hasilnya berhasil dan melempar kesalahan jika Failure. Ini memungkinkan penggunaan Result sebagai jembatan antara gaya fungsional dan berbasis exception: tangani melalui map/flatMap dalam rantai, dan di akhir — get melalui do-catch untuk integrasi dengan kode throws.
Swift Result adalah enum dengan parameter generik, yang memungkinkan kompiler memeriksa penanganan exhaustif melalui switch atau do-catch. Jika Anda menambahkan kasus baru ke enum NetworkError, kompiler akan memberikan kesalahan di semua ekspresi switch di mana kasus ini tidak ditangani. Pemeriksaan exhaustif (exhaustive checking) — keuntungan utama Result dibandingkan exception: kompiler menjamin bahwa semua kemungkinan kesalahan dipertimbangkan pada tahap kompilasi. Sebaliknya, exception tidak diperiksa oleh kompiler di Swift (hanya throws yang dideklarasikan, tetapi bukan tipe kesalahannya).
enum NetworkError: Error {
case badURL
case requestFailed(String)
case decodingFailed
}
func fetchUser(id: Int) -> Result<User, NetworkError> {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
return .failure(.badURL)
}
let result = performRequest(url: url)
switch result {
case let .success(data):
if let user = try? JSONDecoder().decode(User.self, from: data) {
return .success(user)
}
return .failure(.decodingFailed)
case let .failure(error):
return .failure(.requestFailed(error.localizedDescription))
}
}
// Penggunaan dengan switch
let result = fetchUser(id: 42)
switch result {
case .success(let user):
showUser(user)
case .failure(.badURL):
logError("Invalid URL")
case .failure(.requestFailed(let msg)):
showAlert(msg)
case .failure(.decodingFailed):
logError("Decoding error")
}
Result<Success, Failure> memungkinkan mengetik kesalahan pada tingkat tipe: fungsi fetchUser mengembalikan Result<User, NetworkError>, di mana NetworkError adalah enum konkret dengan tiga kasus. Pihak pemanggil menangani setiap kasus melalui switch dengan cakupan exhaustif — kompiler memeriksa apakah semua varian ditangani. Switch exhaustif — keuntungan utama Result dibandingkan exception: kompiler menjamin Anda tidak akan lupa menangani .badURL, .requestFailed atau .decodingFailed. Dalam kasus exception, kompiler tidak memerlukan penanganan, dan catch(.badURL) yang tidak tertangani dapat dengan mudah terlewatkan dalam review.
Di Kotlin Result<T> adalah tipe bawaan dari pustaka standar, yang merepresentasikan sukses (T) atau kesalahan (Throwable). Berbeda dengan Swift, di Kotlin Result tidak mengizinkan menentukan tipe kesalahan spesifik — hanya Throwable. Ini dilakukan untuk kesederhanaan, tetapi memerlukan pemeriksaan tambahan tipe kesalahan melalui ekspresi when. Kotlin Result mendukung fungsi fold (penanganan kedua kasus), getOrNull (sukses atau null), getOrDefault (sukses atau nilai default), map, recover, andThen. Keistimewaan Kotlin: Result tidak dimaksudkan untuk propagasi langsung melintasi batas fungsi — tidak dapat digunakan sebagai tipe kembalian untuk metode Android SDK atau Kotlin Coroutines API tanpa adaptasi tambahan.
fun parseJson(input: String): Result<JsonObject> {
return runCatching {
JsonParser.parseString(input).asJsonObject()
}
}
fun validateEmail(email: String): Result<String> {
return if (email.contains("@")) {
Result.success(email.trim())
} else {
Result.failure(IllegalArgumentException("Invalid email"))
}
}
data class SignupData(val name: String, val email: String)
fun processSignup(name: String, email: String): SignupResult {
return validateEmail(email).fold(
onSuccess = { validateName(name) },
onFailure = { SignupResult.Error("Invalid email") }
)
}
runCatching — pembungkus yang nyaman di Kotlin yang menangkap exception apa pun dan mengembalikan Result.failure. validateEmail mengembalikan Result.success atau Result.failure tergantung pada pemeriksaan. fold menangani kedua kasus secara ringkas: pada Success fungsi validasi berikutnya dipanggil, pada Failure — SignupResult.Error dikembalikan. Penting: Kotlin Result tidak dimaksudkan untuk disimpan di bidang data class atau ditransmisikan langsung melintasi batas fungsi suspend — gunakan sealed class Anda sendiri (Success/Error/Loading) untuk merepresentasikan status UI di Jetpack Compose atau MVVM.
Sejak Dart 3.0 di pustaka standar telah muncul Result<T> bawaan — sealed class dengan dua konstruktor: T.ok() (sukses) dan Error.error() (kesalahan dengan Object dan StackTrace). Sebelum Dart 3.0, pengembang Flutter menggunakan Either<L, R> dari paket dartz atau sealed class khusus. Result bawaan di Dart minimalis: tidak menyediakan map/flatMap secara langsung — fungsi-fungsi ini harus diimplementasikan melalui when atau menggunakan extension methods. Untuk penanganan fungsional yang serius, Either dari fpdart tetap menjadi solusi yang lebih kuat dengan dukungan untuk map, flatMap, mapLeft, fold, andThen dan operator bind.
Either<L, R> — tipe berasosiasi kiri dari paket fpdart, di mana Left — kesalahan, Right — sukses. Berbeda dengan Result<T> bawaan, Either mengetik kesalahan pada tingkat parameter tipe (L), yang memungkinkan membedakan tipe kesalahan pada tahap kompilasi. Paket fpdart menyediakan set lengkap kombinator fungsional: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — Either bersarang), andThen (rantai tanpa transformasi), fold (keluar dari Either), getOrElse (nilai default). Untuk aplikasi Flutter dengan pendekatan fungsional, Either adalah standar de facto.
import 'dart:convert';
import 'package:fpdart/fpdart.dart';
class UserService {
Either<AppError, User> fetchUser(String id) {
try {
final response = await http.get(
Uri.parse('https://api.example.com/users/$id')
);
if (response.statusCode == 200) {
final user = User.fromJson(
json.decode(response.body)
);
return Either.of(user);
}
return Either.left(
AppError.serverError(response.statusCode)
);
} on SocketException catch (e) {
return Either.left(AppError.networkError(e.message));
}
}
}
// Penggunaan dengan fold
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> dari fpdart — tipe berasosiasi kiri: Left — kesalahan, Right — sukses. fetchUser mengembalikan Either<AppError, User>, di mana AppError — sealed class dengan tipe kesalahan spesifik (serverError, networkError). fold menangani kedua kasus: callback pertama untuk Left (kesalahan), kedua untuk Right (sukses). Di Dart 3.0 Result bawaan juga mendukung fold, tetapi tidak menyediakan map/flatMap. Untuk rantai, Either dari fpdart menyediakan map, flatMap (bind), mapLeft, andThen — set lengkap kombinator fungsional untuk komposisi kesalahan.
Keuntungan utama Result dibandingkan exception adalah komposisi. Jika Anda memiliki beberapa operasi, yang masing-masing dapat mengembalikan kesalahan, Anda dapat menggabungkannya dalam rantai melalui map dan flatMap tanpa satu pun if bersarang atau try-catch. map mentransformasi nilai sukses: Result.success(x) -> Result.success(f(x)). flatMap (juga disebut bind atau andThen) — untuk kasus di mana transformasi itu sendiri mengembalikan Result: Result.success(x) -> f(x) -> Result<Y>. Jika pada langkah apa pun terjadi Failure, operasi selanjutnya tidak dijalankan — rantai terputus.
data class UserRequest(val userId: String, val token: String)
sealed class AuthError {
data object InvalidToken : AuthError()
data class UserNotFound(val id: String) : AuthError()
}
typealias Outcome<T> = Either<AuthError, T>
fun validateToken(token: String): Outcome<String> =
if (token.isNotBlank()) Either.right(token)
else Either.left(AuthError.InvalidToken)
fun fetchProfile(userId: String): Outcome<Profile> =
if (userId == "42") Either.right(Profile("Alice"))
else Either.left(AuthError.UserNotFound(userId))
// Komposisi melalui flatMap (andThen di fpdart)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
Rantai: validateToken -> fetchProfile -> map name -> getOrElse “Guest”. Jika validateToken mengembalikan Left (InvalidToken), rantai terputus dan “Guest” dikembalikan. Jika fetchProfile mengembalikan Left (UserNotFound) — juga “Guest”. Jika kedua operasi berhasil — nama profil. flatMap memungkinkan menggabungkan fungsi Either, yang masing-masing dapat gagal, dalam satu rantai linier. Dalam gaya exception tradisional, kode yang sama akan memerlukan dua try-catch bersarang atau pemeriksaan null. getOrElse di akhir — titik keluar dari komposisi, yang menyediakan nilai default untuk kasus Failure.
Result dan exception — bukan pendekatan yang saling eksklusif. Masing-masing memiliki area penerapannya, dan dalam aplikasi mobile yang dirancang dengan baik, keduanya digunakan. Pilihan tergantung pada apakah kesalahan itu diharapkan (expected) atau tidak diharapkan (unexpected). Result — untuk kesalahan yang diharapkan yang merupakan bagian dari logika bisnis: email tidak valid, saldo tidak mencukupi, melebihi batas permintaan. Exception — untuk kesalahan sistem yang tidak diharapkan: kehilangan jaringan, OutOfMemoryError, NullPointerException (yang seharusnya tidak terjadi, tetapi terjadi).
| Kriteria | Result Type | Exception (Exception/Error) |
|---|---|---|
| Tipe kesalahan | Diharapkan (logika bisnis) | Tidak diharapkan (sistem) |
| Kinerja | Biaya rendah (tanpa membuka stack) | Biaya tinggi (stack unwinding, penangkapan StackTrace) |
| Komposisi | Melalui map/flatMap — rantai linier | Try-catch bersarang — sulit dibaca |
| Kompiler | Pemeriksaan exhaustif (switch/when) | Hanya checked exceptions di Java |
| Aliran eksekusi | Tidak terputus — kesalahan sebagai nilai | Terputus hingga catch terdekat |
| Pengujian | Mudah: periksa hasil, assert isSuccess/isError | Memerlukan assertThrows dan objek mock |
| Kapan menggunakan | Validasi bisnis, rantai permintaan, formulir | Kehilangan jaringan, kesalahan I/O, kerusakan sistem |
Aturan praktis: jika kesalahan adalah bagian dari aliran kerja normal aplikasi (pengguna memasukkan email tidak valid, hak akses tidak mencukupi) — gunakan Result. Jika kesalahan adalah situasi luar biasa (server tidak merespons, memori habis) — gunakan exception. Dalam pengembangan mobile Result di batas lapisan (UseCase -> ViewModel) dan exception di dalam lapisan (API -> Repository) — pola umum yang menggabungkan keuntungan kedua pendekatan.
Migrasi kode berbasis exception yang ada ke Result harus bertahap. Mulailah dari batas lapisan: bungkus panggilan fungsi throws dalam Result { try ... } (Swift) atau runCatching { ... } (Kotlin). Kemudian ganti tipe kembalian metode Repository dan UseCase dengan Result/Either, biarkan implementasi internal pada exception. Pada tahap terakhir, migrasikan ViewModel: alih-alih UiState dengan exception, gunakan sealed class UiState<T> (Loading, Success, Error), di mana Error menyimpan kesalahan domain, bukan Throwable. Migrasi bertahap memungkinkan menguji setiap lapisan secara terpisah tanpa refactoring global.
Pertanyaan yang sering diajukan
Optional (T?) merepresentasikan ada atau tidaknya nilai — nil berarti “tidak ada data”, tetapi tidak mengatakan mengapa. Result (Success/Failure) tidak hanya berisi sukses, tetapi juga penyebab kesalahan dengan tipe yang spesifik. Gunakan Optional ketika ketiadaan nilai adalah normal (misalnya, bidang profil opsional), Result — ketika Anda membutuhkan informasi tentang kesalahan.
Di Swift gunakan Result { try throwingFunc() } — konstruktor Result menerima closure throws. Di Kotlin — runCatching { throwingFunc() }, yang mengembalikan Result<T>. Di Dart — Result<T>.tryCatch(() => throwingFunc()). Ini memungkinkan integrasi mudah kode berbasis exception ke dalam rantai Result.
Gunakan mapError (Swift) atau mapLeft (Either di Dart/Kotlin) untuk transformasi tipe kesalahan tanpa mengubah nilai sukses. Jika perlu menangani kedua kasus dan mengembalikan nilai tunggal — gunakan fold. Untuk pencatatan tanpa memutus rantai, gunakan onFailure (Kotlin) atau inspection point prefiks.
Ya, tetapi dengan hati-hati. Kotlin Result<T> tidak direkomendasikan sebagai tipe kembalian langsung fungsi suspend karena fitur K2 compiler dan refleksi. Gunakan sealed class khusus NetworkResult<T> (Success, Error, Loading) untuk merepresentasikan status di coroutine. Untuk kesalahan yang diharapkan dalam logika bisnis, Either dari Arrow adalah alternatif yang lebih kuat.
fold — metode yang menerima dua callback: onSuccess (untuk kasus sukses) dan onFailure (untuk kasus kesalahan), dan mengembalikan nilai tunggal dari tipe apa pun. Ini adalah padanan dari ekspresi switch, tetapi dalam bentuk fungsi tingkat tinggi. fold — titik keluar utama dari rantai Result, di mana Anda mengubah Success/Failure menjadi UiState, string untuk pengguna, atau Result lainnya.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga