Result Type — یک نوع-کانتینر است که نتیجه عملیاتی را نشان میدهد که میتواند با موفقیت (Success) یا خطا (Failure) پایان یابد. برخلاف استثناها، Result خطا را به عنوان یک مقدار معمولی بدون باز کردن پشته منتقل میکند، که این امر پردازش خطاهای مورد انتظار را ایمنتر و ترکیبپذیرتر میکند. طبق Apple Swift Documentation (2026)، Result<Success, Failure> در Swift امکان ترکیب زنجیرهای عملیات با پردازش خودکار خطا از طریق map و flatMap را بدون قطع کردن اجرای برنامه فراهم میکند.
نکات اصلی
Result Type یک نوع جنریک است که نتیجه اجرای عملیاتی را کپسوله میکند که میتواند هم با موفقیت و هم با خطا پایان یابد. برخلاف استثناها، جایی که خطا جریان عادی اجرا را قطع میکند و برای یافتن catch نیاز به باز کردن پشته دارد، Result خطا را به عنوان یک مقدار معمولی منتقل میکند — طرف فراخواننده همیشه یک شی دریافت میکند و خودش تصمیم میگیرد با آن چه کند. این به ویژه برای خطاهای مورد انتظار مفید است: ورودی نامعتبر، قوانین تجاری، رد سرور، جایی که استثنا مکانیسم بسیار سنگینی خواهد بود.
مفهوم Result ریشه در برنامهنویسی تابعی دارد، جایی که انواع مشابه Either (یا Left/Right) نامیده میشوند. در Swift، Result به یک نوع استاندارد در Swift 5.0 تبدیل شد، در Kotlin Result<T> در کتابخانه استاندارد ظاهر شد، در Dart 3.0 — Result<T> داخلی. هر پیادهسازی متدهایی برای کار با کانتینر فراهم میکند: map (تبدیل مقدار موفقیت)، flatMap (زنجیره توابع Result)، mapError (تبدیل خطا)، fold (پردازش هر دو حالت). Result Type — سنگ بنای پردازش خطای تابعی در توسعه موبایل، جایگزینی برای try-catch برای سناریوهای مورد انتظار است.
مزیت کلیدی Result ترکیبپذیری است. میتوانید چندین عملیات را که هر کدام ممکن است خطا برگردانند، در یک زنجیره بدون try-catchهای تودرتو ترکیب کنید. اگر هر عملیاتی در زنجیره با Failure پایان یابد، کل زنجیره قطع شده و Failure برمیگرداند — بدون یک شرط if یا بلوک catch. این کار کد را خطی و خوانا میکند، به ویژه در سناریوهای با درخواستهای متوالی متعدد به API یا بررسی قوانین تجاری.
در Swift Result<Success, Failure> یک enum با دو حالت است: .success(Success) و .failure(Failure)، که در آن Failure توسط پروتکل Error محدود شده است. Swift Result یک نوع تابعی کامل با متدهای map, flatMap, mapError و get است. get یک متد خاص است: مقدار Success را در صورت موفقیتآمیز بودن نتیجه برمیگرداند و در صورت Failure خطا پرتاب میکند. این امکان استفاده از Result را به عنوان پلی بین سبکهای تابعی و مبتنی بر استثنا فراهم میکند: در زنجیره از طریق map/flatMap پردازش کنید و در پایان get را از طریق do-catch برای ادغام با کد throws استفاده کنید.
Swift Result یک enum با پارامترهای جنریک است که به کامپایلر اجازه میدهد پردازش جامع را از طریق switch یا do-catch بررسی کند. اگر یک حالت جدید به enum NetworkError اضافه کنید، کامپایلر در تمام عبارات switch که این حالت در آنها پردازش نشده است، خطا میدهد. بررسی جامع (exhaustive checking) — مزیت اصلی Result نسبت به استثناها: کامپایلر تضمین میکند که تمام خطاهای ممکن در مرحله کامپایل در نظر گرفته شدهاند. در مقابل، استثناها در Swift توسط کامپایلر بررسی نمیشوند (فقط throws اعلام میشود، اما نوع خطا نه).
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))
}
}
// استفاده با 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> امکان تایپبندی خطا در سطح نوع را فراهم میکند: تابع fetchUser Result<User, NetworkError> برمیگرداند، که در آن NetworkError یک enum مشخص با سه حالت است. طرف فراخواننده هر حالت را از طریق switch با پوشش جامع پردازش میکند — کامپایلر بررسی میکند که همه گزینهها پردازش شدهاند. switch جامع — مزیت کلیدی Result نسبت به استثناها: کامپایلر تضمین میکند که پردازش .badURL، .requestFailed یا .decodingFailed را فراموش نخواهید کرد. در مورد استثناها، کامپایلر به پردازش نیاز ندارد و catch(.badURL) پردازشنشده به راحتی در بازبینی نادیده گرفته میشود.
در Kotlin Result<T> یک نوع داخلی از کتابخانه استاندارد است که موفقیت (T) یا خطا (Throwable) را نشان میدهد. برخلاف Swift، در Kotlin Result اجازه تعیین نوع خاص خطا را نمیدهد — فقط Throwable. این کار برای سادگی انجام شده است، اما نیاز به بررسی اضافی نوع خطا از طریق عبارت when دارد. Kotlin Result از توابع fold (پردازش هر دو حالت)، getOrNull (موفقیت یا null)، getOrDefault (موفقیت یا مقدار پیشفرض)، map، recover، andThen پشتیبانی میکند. ویژگی Kotlin: Result برای انتشار مستقیم از طریق مرزهای توابع طراحی نشده است — بدون تطبیق اضافی نمیتوان از آن به عنوان نوع بازگشتی برای متدهای Android SDK یا Kotlin Coroutines API استفاده کرد.
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 — یک پوشش راحت در Kotlin است که هر استثنایی را گرفته و Result.failure برمیگرداند. validateEmail بسته به بررسی Result.success یا Result.failure برمیگرداند. fold هر دو حالت را به صورت فشرده پردازش میکند: در Success تابع اعتبارسنجی بعدی فراخوانی میشود، در Failure — SignupResult.Error برگردانده میشود. مهم: Kotlin Result برای ذخیره در فیلدهای data class یا انتقال مستقیم از طریق مرزهای توابع suspend طراحی نشده است — از sealed classهای خود (Success/Error/Loading) برای نمایش حالتهای UI در Jetpack Compose یا MVVM استفاده کنید.
از Dart 3.0 در کتابخانه استاندارد، Result<T> داخلی ظاهر شد — یک sealed class با دو سازنده: T.ok() (موفقیت) و Error.error() (خطا با Object و StackTrace). قبل از Dart 3.0، توسعهدهندگان Flutter از Either<L, R> از بسته dartz یا sealed classهای سفارشی استفاده میکردند. Result داخلی در Dart حداقلی است: مستقیماً map/flatMap را ارائه نمیدهد — این توابع باید از طریق when یا با استفاده از extension methods پیادهسازی شوند. برای پردازش تابعی جدی، Either از fpdart با پشتیبانی از map، flatMap، mapLeft، fold، andThen و عملگرهای bind راهحل قدرتمندتری باقی میماند.
Either<L, R> یک نوع چپ-مرتبط از بسته fpdart است، که در آن Left — خطا، Right — موفقیت. برخلاف Result<T> داخلی، Either خطا را در سطح پارامتر نوع (L) تایپبندی میکند که امکان تشخیص انواع خطا را در مرحله کامپایل فراهم میکند. بسته fpdart مجموعه کاملی از ترکیبکنندههای تابعی را فراهم میکند: map (Right -> Right)، mapLeft (Left -> Left)، flatMap (bind — Eitherهای تودرتو)، andThen (زنجیره بدون تبدیل)، fold (خروج از Either)، getOrElse (مقدار پیشفرض). برای برنامههای Flutter با رویکرد تابعی، Either استاندارد دوفاکتو است.
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));
}
}
}
// استفاده با fold
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> از fpdart — نوع چپ-مرتبط: Left — خطا، Right — موفقیت. fetchUser Either<AppError, User> برمیگرداند، که در آن AppError — sealed class با انواع خطای مشخص (serverError, networkError). fold هر دو حالت را پردازش میکند: callback اول برای Left (خطا)، دومی برای Right (موفقیت). در Dart 3.0 Result داخلی نیز از fold پشتیبانی میکند، اما map/flatMap را ارائه نمیدهد. برای زنجیرههای Either از fpdart، map، flatMap (bind)، mapLeft، andThen — مجموعه کاملی از ترکیبکنندههای تابعی برای ترکیب خطاها در دسترس است.
مزیت اصلی Result نسبت به استثناها — ترکیبپذیری است. اگر چندین عملیات دارید که هر کدام ممکن است خطا برگردانند، میتوانید آنها را در یک زنجیره از طریق map و flatMap بدون یک if تودرتو یا try-catch ترکیب کنید. map مقدار موفقیت را تبدیل میکند: Result.success(x) -> Result.success(f(x)). flatMap (همچنین bind یا andThen نامیده میشود) — برای مواردی که خود تبدیل Result برمیگرداند: Result.success(x) -> f(x) -> Result<Y>. اگر در هر مرحلهای Failure رخ دهد، عملیات بعدی اجرا نمیشود — زنجیره قطع میشود.
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))
// ترکیب از طریق flatMap (andThen در fpdart)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
زنجیره: validateToken -> fetchProfile -> map name -> getOrElse «Guest». اگر validateToken Left (InvalidToken) برگرداند، زنجیره قطع میشود و «Guest» برگردانده میشود. اگر fetchProfile Left (UserNotFound) برگرداند — همچنین «Guest». اگر هر دو عملیات موفق باشند — نام پروفایل. flatMap امکان ترکیب توابع Either را که هر کدام ممکن است شکست بخورند، در یک زنجیره خطی فراهم میکند. در سبک سنتی استثنا، همان کد نیاز به دو try-catch تودرتو یا بررسی null داشت. getOrElse در پایان — نقطه خروج از ترکیب است که مقدار پیشفرض را برای حالت Failure فراهم میکند.
Result و استثناها — رویکردهای متقابلاً انحصاری نیستند. هر کدام حوزه کاربردی خود را دارد و در یک برنامه موبایل خوب طراحی شده از هر دو استفاده میشود. انتخاب بستگی به این دارد که آیا خطا مورد انتظار (expected) است یا غیرمنتظره (unexpected). Result — برای خطاهای مورد انتظار که بخشی از منطق تجاری هستند: ایمیل نامعتبر، موجودی ناکافی در حساب، تجاوز از محدودیت درخواست. استثناها — برای خطاهای سیستمی غیرمنتظره: قطع شبکه، OutOfMemoryError، NullPointerException (که نباید باشند، اما اتفاق میافتند).
| معیار | Result Type | استثناها (Exception/Error) |
|---|---|---|
| نوع خطا | مورد انتظار (منطق تجاری) | غیرمنتظره (سیستمی) |
| عملکرد | هزینه کم (بدون باز کردن پشته) | هزینه بالا (باز کردن پشته، ضبط StackTrace) |
| ترکیب | از طریق map/flatMap — زنجیرههای خطی | try-catchهای تودرتو — خواندن دشوار |
| کامپایلر | بررسی جامع (switch/when) | فقط checked exceptions در Java |
| جریان اجرا | قطع نمیشود — خطا به عنوان مقدار | تا نزدیکترین catch قطع میشود |
| تست | آسان: بررسی نتیجه، assert isSuccess/isError | نیاز به assertThrows و اشیاء mock دارد |
| چه زمانی استفاده کنیم | اعتبارسنجی تجاری، زنجیره درخواستها، فرمها | قطع شبکه، خطاهای I/O، خرابی سیستم |
قاعده عملی: اگر خطا بخشی از جریان عادی کار برنامه است (کاربر ایمیل نامعتبر وارد کرده، حقوق دسترسی کافی نیست) — از Result استفاده کنید. اگر خطا یک وضعیت استثنایی است (سرور پاسخ نمیدهد، حافظه تمام شده) — از استثناها استفاده کنید. در توسعه موبایل Result در مرزهای لایهها (UseCase -> ViewModel) و استثناها در داخل لایهها (API -> Repository) — یک الگوی رایج است که مزایای هر دو رویکرد را ترکیب میکند.
مهاجرت کد موجود مبتنی بر استثنا به Result باید تدریجی باشد. از مرزهای لایهها شروع کنید: فراخوانیهای توابع throws را در Result { try ... } (Swift) یا runCatching { ... } (Kotlin) بپیچید. سپس نوع بازگشتی متدهای Repository و UseCase را با Result/Either جایگزین کنید، پیادهسازی داخلی را بر روی استثناها نگه دارید. در مرحله آخر ViewModel را مهاجرت کنید: به جای UiState با استثناها از sealed class UiState<T> (Loading, Success, Error) استفاده کنید، که در آن Error خطای دامنه را ذخیره میکند نه Throwable را. مهاجرت تدریجی امکان تست هر لایه را به صورت جداگانه بدون بازآرایی سراسری فراهم میکند.
سوالات متداول
Optional (T?) وجود یا عدم وجود مقدار را نشان میدهد — nil به معنای "داده وجود ندارد" است، اما دلیل را نمیگوید. Result (Success/Failure) نه تنها موفقیت، بلکه علت خطا را با نوع مشخصی包含 میکند. زمانی که عدم وجود مقدار طبیعی است (مثلاً فیلد اختیاری پروفایل) از Optional استفاده کنید، زمانی که به اطلاعات خطا نیاز دارید — از Result.
در Swift از Result { try throwingFunc() } استفاده کنید — سازنده Result یک بسته throws میگیرد. در Kotlin — runCatching { throwingFunc() } که Result<T> برمیگرداند. در Dart — Result<T>.tryCatch(() => throwingFunc()). این امکان ادغام آسان کد مبتنی بر استثنا را در زنجیرههای Result فراهم میکند.
از mapError (Swift) یا mapLeft (Either در Dart/Kotlin) برای تبدیل نوع خطا بدون تغییر مقدار موفقیت استفاده کنید. اگر نیاز به پردازش هر دو حالت و بازگرداندن یک مقدار واحد دارید — از fold استفاده کنید. برای ثبت وقایع بدون قطع زنجیره از onFailure (Kotlin) یا inspection point پیشوندی استفاده کنید.
بله، اما با احتیاط. Kotlin Result<T> به دلیل ویژگیهای K2 compiler و بازتاب به عنوان نوع بازگشتی مستقیم توابع suspend توصیه نمیشود. از sealed class سفارشی NetworkResult<T> (Success, Error, Loading) برای نمایش حالتها در کوروتینها استفاده کنید. برای خطاهای مورد انتظار در منطق تجاری، Either از Arrow جایگزین قدرتمندتری است.
fold — متدی است که دو callback میگیرد: onSuccess (برای حالت موفقیت) و onFailure (برای حالت خطا)، و یک مقدار واحد از هر نوعی برمیگرداند. این معادل عبارت switch است، اما به صورت تابع مرتبه بالاتر. fold — نقطه خروج اصلی از زنجیرههای Result است، جایی که Success/Failure را به UiState، رشته برای کاربر یا Result دیگر تبدیل میکنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید