Error Propagation एक तंत्र है जो त्रुटि को उत्पत्ति स्थान से ऊपर कॉल स्टैक में हैंडलर तक प्रसारित करता है। जब कोई फ़ंक्शन स्वयं त्रुटि को संभाल नहीं सकता, तो वह इसे अपवाद (exception), throws-घोषणा या Return Type के माध्यम से कॉल करने वाले पक्ष को भेजता है। मोबाइल एप्लिकेशन की स्थिरता के लिए propagation का सही कार्यान्वयन महत्वपूर्ण है: अनुपचारित या गलत तरीके से पारित त्रुटियाँ क्रैश का कारण बनती हैं। Apple Swift Documentation (2026) के अनुसार, Swift में throws के माध्यम से स्वचालित propagation बिना boilerplate-कोड के किसी भी स्तर पर त्रुटि भेजने की अनुमति देता है।
मुख्य बातें
Error Propagation (त्रुटि प्रसार) त्रुटि ऑब्जेक्ट को उस फ़ंक्शन से जहाँ वह उत्पन्न हुई, कॉल श्रृंखला में ऊपर निकटतम उपयुक्त हैंडलर तक भेजने की प्रक्रिया है। कॉल स्टैक की कल्पना करें: ViewController ViewModel को कॉल करता है, ViewModel Repository को कॉल करता है, Repository API को कॉल करता है। यदि API नेटवर्क त्रुटि लौटाता है, तो इसे Repository और ViewModel के माध्यम से ViewController तक जाना होगा, जो उपयोगकर्ता को संदेश दिखाएगा। प्रत्येक मध्यवर्ती फ़ंक्शन निर्णय लेता है: त्रुटि को संभालें या आगे भेजें (propagate)।
Propagation के दो दृष्टिकोण हैं: स्वचालित और मैन्युअल। स्वचालित दृष्टिकोण (Swift throws, Java checked exceptions) में कंपाइलर डेवलपर को या तो त्रुटि संभालने या हस्ताक्षर में propagation घोषित करने के लिए बाध्य करता है। मैन्युअल दृष्टिकोण (Result Type, Kotlin Try) में त्रुटि को मान के रूप में भेजा जाता है — डेवलपर स्पष्ट रूप से त्रुटि भेजने या रूपांतरित करने के लिए कोड लिखता है। Kotlin Result Docs (2026) के अनुसार, Kotlin में Result<T> सीधे फ़ंक्शन सीमाओं के पार propagation के लिए अभिप्रेत नहीं है — इसे प्रत्येक स्तर पर रूपांतरित या संभालने की आवश्यकता होती है, जो propagation को अधिक सचेत लेकिन अधिक शब्दाडंबरपूर्ण बनाता है।
दृष्टिकोण का चुनाव एप्लिकेशन आर्किटेक्चर और भाषा पर निर्भर करता है। Swift में स्वचालित propagation का throws के माध्यम से प्रभुत्व है, Kotlin में अपवादों (अप्रत्याशित त्रुटियों के लिए) और Result-जैसे कंटेनरों (अपेक्षित त्रुटियों के लिए) का मिश्रण उपयोग होता है। यह समझना महत्वपूर्ण है: propagation लक्ष्य नहीं है, बल्कि आवश्यकता है। एक आदर्श आर्किटेक्चर propagation की गहराई को कम करता है, त्रुटियों को सबसे निचले संभव स्तर पर संभालता है जहाँ निर्णय लेने के लिए पर्याप्त संदर्भ हो।
Swift में, throws के माध्यम से propagation स्वचालित है: यदि फ़ंक्शन A (throws के साथ) फ़ंक्शन B (throws के साथ) को कॉल करता है, और A, B की त्रुटि को do-catch में नहीं संभालता, तो त्रुटि स्वचालित रूप से A के कॉल करने वाले को भेज दी जाती है। यह Java checked exceptions के विशिष्ट boilerplate कोड को समाप्त करता है, जहाँ श्रृंखला के प्रत्येक विधि में throws घोषित करना आवश्यक होता है। Swift सिद्धांत का उपयोग करता है «श्रृंखला में एक throws-फ़ंक्शन = पूरी श्रृंखला throws बन जाती है, यदि मध्यवर्ती स्तरों पर संभाला न जाए»।
struct UserRepository {
func fetchUser(id: Int) throws -> User {
let data = try networkService.request(path: "/users/\(id)")
return try parseUser(from: data)
}
}
class UserViewModel {
let repo = UserRepository()
func loadUser(id: Int) throws -> User {
return try repo.fetchUser(id: id)
}
}
// ViewController — अंतिम हैंडलर
func onButtonTap() {
let vm = UserViewModel()
do {
let user = try vm.loadUser(id: 42)
updateUI(user)
} catch {
showError("उपयोगकर्ता लोड करने में विफल")
}
}
Propagation श्रृंखला: networkService.request -> fetchUser -> loadUser -> onButtonTap। प्रत्येक मध्यवर्ती फ़ंक्शन throws से चिह्नित है और इसमें do-catch नहीं है — त्रुटि स्वचालित रूप से ऊपर भेजी जाती है। ViewController onButtonTap do-catch के साथ अंतिम हैंडलर है। यदि ViewModel त्रुटि को रूपांतरित करने का निर्णय लेता (दूसरे प्रकार में लपेटना), तो वह do-catch और नया throw उपयोग कर सकता था। स्वचालित propagation कोड को कम करता है: Repository को यह जानने की आवश्यकता नहीं है कि त्रुटि कैसे संभालें — यह ViewController की जिम्मेदारी है, जिसके पास उपयोगकर्ता को संदेश दिखाने के लिए UI तक पहुँच है।
Kotlin में, अपवादों के माध्यम से propagation के लिए हस्ताक्षर में throws घोषणा की आवश्यकता नहीं है (सभी अपवाद unchecked हैं)। अपवाद स्वचालित रूप से स्टैक में ऊपर उठता है जब तक कि वह try-catch से न मिल जाए। हालाँकि, हस्ताक्षर में throws की अनुपस्थिति propagation को अंतर्निहित बनाती है: डेवलपर फ़ंक्शन के हस्ताक्षर से नहीं देख सकता कि वह अपवाद फेंक सकता है। यह एक साथ प्लस (कम boilerplate) और माइनस (संभालना भूलना आसान) दोनों है। Kotlin इस समस्या को सम्मेलनों और आर्किटेक्चरल पैटर्न के माध्यम से हल करता है, न कि भाषा के माध्यम से।
class UserRepository(
private val api: ApiService,
private val db: Database
) {
suspend fun getUser(id: String): User {
return try {
api.fetchUser(id)
} catch (e: IOException) {
db.getCachedUser(id) ?: throw AppException("User unavailable")
}
}
}
class UserViewModel(private val repo: UserRepository) {
private val _state = MutableStateFlow<UiState<User>>(UiState.Loading)
val state: StateFlow<UiState<User>> = _state
fun loadUser(id: String) {
viewModelScope.launch {
try {
val user = repo.getUser(id)
_state.value = UiState.Success(user)
} catch (e: AppException) {
_state.value = UiState.Error(e.message ?: "Unknown")
}
}
}
}
Repository में, रूपांतरण के साथ propagation: IOException (नेटवर्क अनुपलब्ध) पर फ़ंक्शन डेटाबेस से कैश्ड डेटा प्राप्त करने का प्रयास करता है। यदि कैश खाली है, तो AppException फेंकता है — propagation नए त्रुटि प्रकार के साथ जारी रहती है। ViewModel AppException को पकड़ता है और UiState.Error में अनुवादित करता है — त्रुटि आगे नहीं जाती, propagation UI-स्तर पर समाप्त होती है। Kotlin Coroutines विशेषताएँ जोड़ते हैं: launch में अपवाद स्वचालित रूप से CoroutineExceptionHandler के माध्यम से प्रसारित होते हैं, और async में — केवल await() कॉल करने पर। कोरूटीन में propagation डिज़ाइन करते समय यह महत्वपूर्ण है — SupervisorJob चाइल्ड कोरूटीन में त्रुटि पर पैरेंट कोरूटीन के रद्दीकरण को रोकता है।
अपवादों का विकल्प कंटेनर प्रकार के माध्यम से propagation है जो सफलता या त्रुटि को मान के रूप में भेजता है। इस दृष्टिकोण में, फ़ंक्शन मान नहीं बल्कि एक आवरण लौटाता है: Swift में Result<T, E>, Kotlin में Result<T>, Dart में Either<L, R> (fpdart या dartz पैकेज से)। त्रुटि स्टैक को अनवाइंड नहीं करती — वह बस कंटेनर में रहती है, और अगला स्तर निर्णय लेता है कि उसके साथ क्या करना है। यह propagation को अधिक स्पष्ट और नियंत्रित बनाता है।
data class HttpResult<out T>(
val data: T?,
val error: AppError?
) {
val isSuccess: Boolean get() = data != null
val isError: Boolean get() = error != null
}
sealed class AppError {
data class Network(val message: String) : AppError()
data class Auth(val message: String) : AppError()
}
fun fetchUser(id: String): HttpResult<User> {
return try {
val response = api.get("/users/$id")
HttpResult(data = parseUser(response), error = null)
} catch (e: IOException) {
HttpResult(data = null, error = AppError.Network("No internet"))
}
}
HttpResult<T> — data और error फ़ील्ड वाला सरल कंटेनर। Sealed class AppError त्रुटि प्रकार (Network, Auth) परिभाषित करता है। fetchUser फ़ंक्शन HttpResult लौटाता है, propagation में स्टैक अनवाइंडिंग की आवश्यकता नहीं है — कॉल करने वाला बस isSuccess/isError की जाँच करता है। यह दृष्टिकोण विशेष रूप से Clean Architecture में उपयोगी है, जहाँ प्रत्येक परत (data, domain, presentation) त्रुटि को रूपांतरित कर सकती है: IOError -> DomainError -> UiError। कंटेनर के माध्यम से propagation इन रूपांतरणों को स्पष्ट और परीक्षण योग्य बनाता है, अपवादों के विपरीत जहाँ रूपांतरण श्रृंखला फ़ंक्शन हस्ताक्षरों में दिखाई नहीं देती।
त्रुटि हैंडलिंग डिज़ाइन में एक प्रमुख निर्णय propagation (ऊपर भेजना) और handling (यहाँ संभालना) के बीच चुनाव है। निर्णय नियम: त्रुटि को उस स्तर पर संभालें जहाँ सार्थक कार्रवाई के लिए पर्याप्त संदर्भ हो। यदि आपके पास UI तक पहुँच है — उपयोगकर्ता को संदेश दिखाएँ। यदि आपके पास कैश तक पहुँच है — पुनर्प्राप्ति का प्रयास करें। यदि कोई नहीं है — propagate करें।
| परिदृश्य | कार्रवाई | तर्क |
|---|---|---|
| Repository में नेटवर्क त्रुटि | Propagate | Repository नहीं जानता कि उपयोगकर्ता अनुरोध दोहराना चाहता है या नहीं |
| Repository में पार्सिंग त्रुटि | संभालें (डिफ़ॉल्ट लौटाएँ) | Repository प्रारूप जानता है, फ़ॉलबैक मान लौटा सकता है |
| ViewModel में टाइमआउट | संभालें (UiState.Error) | ViewModel UiState प्रबंधित करता है, त्रुटि का अनुवाद करना जानता है |
| Interceptor में प्राधिकरण त्रुटि | संभालें (टोकन ताज़ा करें) | Interceptor के पास टोकन तक पहुँच है और सत्र पुनर्स्थापित कर सकता है |
| UseCase में अज्ञात त्रुटि | Propagate | UseCase के पास UI संदर्भ नहीं है — केवल व्यावसायिक तर्क |
सुनहरा नियम: न्यूनतम propagation, निचले स्तरों पर अधिकतम handling। यदि Repository कैश से पुनर्प्राप्त कर सकता है — उसे त्रुटि को ऊपर भेजे बिना ऐसा करना चाहिए। यदि ViewModel Snackbar दिखा सकता है — उसे दिखाने दें, ViewController से अतिरिक्त कोड की आवश्यकता के बिना। Propagation का प्रत्येक स्तर युग्मन बढ़ाता है और परीक्षण को जटिल बनाता है। Google Android Architecture Guide (2026) के अनुसार, ViewModel स्तर पर सभी संभावित स्थितियों (Loading, Success, Error) का प्रतिनिधित्व करने के लिए sealed class UiState का उपयोग करके और सीधे UI परत में अपवादों को पारित न करके परत सीमाओं के पार propagation को कम करने की अनुशंसा की जाती है।
गलत propagation मोबाइल एप्लिकेशन में मुश्किल से पकड़ में आने वाली बगों का स्रोत है। आइए पाँच मुख्य समस्याओं पर विचार करें जिनका डेवलपर्स सामना करते हैं और उन्हें हल करने के तरीके।
सबसे आम समस्या: propagation के दौरान, अपवाद को पकड़ा जाता है, लॉग किया जाता है और मूल अपवाद के बिना नया फेंका जाता है। डेवलपर StackTrace खो देता है और समझ नहीं पाता कि त्रुटि वास्तव में कहाँ हुई। Swift में, error chaining का उपयोग करें: throw MyError(context: originalError)। Kotlin में: throw AppException(cause = originalException)। Dart में: throw AppException(message, originalException)। cause/underlyingError पारित किए बिना कभी भी नया अपवाद न बनाएँ।
catch (e: Exception) { /* कुछ नहीं */ } — एक एंटीपैटर्न जो एप्लिकेशन को गलत स्थिति में काम जारी रखने का कारण बनता है। यदि आप सुनिश्चित हैं कि त्रुटि को अनदेखा किया जा सकता है — औचित्य के साथ टिप्पणी जोड़ें। Swift में वैकल्पिक अनदेखी के लिए try? (त्रुटि -> nil) का उपयोग करें। Kotlin में — Result<T>.onFailure { /* लॉग */ }। बिना लॉगिंग के अपवादों को न निगलें।
यदि त्रुटि बिना संभाले 5+ स्तरों से गुज़रती है, तो आर्किटेक्चर को पुनर्विचार की आवश्यकता है। Propagation का प्रत्येक स्तर अंतर्निहित फ़ंक्शनों के throws हस्ताक्षर पर निर्भरता है। समाधान: परत सीमाओं पर Failure-कंटेनर (sealed class Result { Success, Error }) का उपयोग करें ताकि propagation स्पष्ट और सीमित हो। Propagation श्रृंखला जितनी छोटी होगी, कोड का परीक्षण और डिबगिंग उतना ही आसान होगा।
Kotlin Coroutines में, launch में अपवाद डिफ़ॉल्ट रूप से पैरेंट कोरूटीन और सभी siblings (उसी scope के बच्चे) को रद्द कर देता है। यदि 10 समानांतर कार्यों में से एक विफल होता है, तो अन्य 9 रद्द हो जाएँगे, जो अक्सर अवांछनीय है। त्रुटियों को अलग करने के लिए SupervisorJob या supervisorScope का उपयोग करें: एक बच्चे में त्रुटि siblings को रद्द नहीं करती। ViewModelScope डिफ़ॉल्ट रूप से SupervisorJob का उपयोग करता है, जो Android में इस समस्या से बचाता है।
callback-आधारित API में, त्रुटि अक्सर कॉलबैक के पैरामीटर के रूप में भेजी जाती है। यदि callback त्रुटि को नहीं संभालता (या गलत तरीके से संभालता है), तो propagation अंतर्निहित हो जाती है और आसानी से खो जाती है। समाधान: async/await (Swift) या कोरूटीन (Kotlin) पर माइग्रेट करें, जहाँ propagation मानक try-catch तंत्र के माध्यम से काम करता है। यदि callback अपरिहार्य है — दोनों मामलों को संभालने के लिए Either<Error, T> या Result<T> का उपयोग करें।
अक्सर पूछे जाने वाले प्रश्न
Throw एक बार की कार्रवाई है जो अपवाद फेंकती है। Error Propagation throw से catch तक कई स्टैक स्तरों के माध्यम से त्रुटि भेजने की पूरी प्रक्रिया है। Propagation में throw, मध्यवर्ती फ़ंक्शनों के माध्यम से स्वचालित या मैन्युअल भेजना और अंतिम हैंडलिंग शामिल है। यह एक व्यापक अवधारणा है जो त्रुटि के जीवनचक्र का वर्णन करती है।
Mock-ऑब्जेक्ट का उपयोग करें जो दिए गए परिदृश्यों में अपवाद फेंकते हैं। जाँच करें कि फ़ंक्शन सही ढंग से propagate या त्रुटि को संभालता है assertThrows (Kotlin/JUnit) या XCTAssertThrowsError (Swift/XCTest) के माध्यम से। Result-आधारित propagation के लिए, isSuccess/isError और दोनों मामलों में मानों की जाँच करें।
Result propagation एक आर्किटेक्चरल सीमा के भीतर अपेक्षित त्रुटियों (अमान्य डेटा, व्यावसायिक नियम) के लिए बेहतर है। अपवाद अप्रत्याशित त्रुटियों (नेटवर्क हानि, I/O त्रुटियाँ) के लिए बेहतर हैं जिन्हें उच्च स्तर पर संभाला जाना चाहिए। त्रुटि वाला परिणाम निष्पादन प्रवाह को बाधित नहीं करता, अपवाद बाधित करता है।
Kotlin Coroutines में, launch में अपवाद स्वचालित रूप से siblings के रद्दीकरण के साथ CoroutineScope के माध्यम से propagate होता है। अलगाव के लिए supervisorScope या SupervisorJob का उपयोग करें: एक कोरूटीन में त्रुटि दूसरों को रद्द नहीं करती। async के लिए, await() कॉल करते समय try-catch के माध्यम से त्रुटि को स्पष्ट रूप से संभाला जाना चाहिए, अन्यथा इसे निगल लिया जाएगा।
आदर्श अंतिम हैंडलर UI परत (ViewController, Fragment/Composable) है। केवल उसके पास उपयोगकर्ता इंटरफ़ेस तक पहुँच है और वह संदेश, Snackbar या डायलॉग दिखा सकता है। मध्यवर्ती परतें (Repository, UseCase, ViewModel) त्रुटि को propagate करती हैं, यदि आवश्यक हो तो अधिक सार डोमेन प्रकार में रूपांतरित करती हैं।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें