← Назад к вопросам

Можно ли дождаться результата в launch?

1.7 Middle🔥 251 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли дождаться результата в launch?

Короткий ответ: Нет, launch не возвращает результат. Для этого используй async().

Но есть несколько способов получить результат из launch, если нужно.

1. launch — запустить и забыть

launch не возвращает результат. Он используется для side-effects (побочных эффектов):

launch {
    // Это выполняется, но результат мы не получаем
    val data = api.fetch()
    updateUI(data)
    // Корутина завершается, результата нет
}

// Ничего не ждём, продолжаем работу дальше
println("Началась загрузка")

Возвращаемый тип:

fun launch(...): Job  // Только Job, без результата!

2. async — получить результат

async возвращает Deferred, из которого можно получить результат:

val deferred = async {
    api.fetch() // Возвращаем результат
}

// Ждём результат
val result = deferred.await()
println(result)

Возвращаемый тип:

fun async<T>(...): Deferred<T>  // Можно получить T через await()

3. Как получить результат из launch — неправильные способы

❌ Способ 1: Обычная переменная (race condition)

var data: String? = null

launch {
    delay(1000)
    data = api.fetch()  // Устанавливает через 1 сек
}

println(data)  // ❌ null! (не ждём завершения launch)

Проблема: главный поток не ждёт корутину!

❌ Способ 2: Thread.sleep (блокирующий)

var data: String? = null

launch {
    delay(1000)
    data = api.fetch()
}

Thread.sleep(2000)  // ❌ ОЧЕНЬ плохо! Блокируем главный поток
println(data)       // Может быть null если sleep < delay

Проблема: UI замирает, приложение не отзывается!

4. Правильные способы получить результат

✅ Способ 1: Использовать async() вместо launch

fun loadData() {
    viewModelScope.launch {
        val data = async {
            api.fetch()  // Получим результат
        }.await()
        
        state.value = data
    }
}

✅ Способ 2: Callback в launch

fun loadData(onSuccess: (String) -> Unit) {
    viewModelScope.launch {
        try {
            val data = api.fetch()
            onSuccess(data)  // Возвращаем через callback
        } catch (e: Exception) {
            onError(e)
        }
    }
}

// Использование
loadData { data ->
    state.value = data
}

✅ Способ 3: LiveData / StateFlow (реактивный)

class UserViewModel : ViewModel() {
    private val _state = MutableLiveData<User?>()
    val state: LiveData<User?> = _state
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            try {
                val user = repository.getUser(id)
                _state.value = user  // Результат здесь
            } catch (e: Exception) {
                _state.value = null
            }
        }
    }
}

// Использование (Activity/Fragment)
viewModel.state.observe(this) { user ->
    if (user != null) {
        updateUI(user)
    }
}

5. Таблица: launch vs async

Параметрlaunchasync
ВозвращаетJobDeferred<T>
РезультатНетЕсть (await())
ДляSide-effectsВычисления с результатом
ОшибкиИгнорируютсяВыбрасываются на await()
ПримерЗагрузка, сохранениеПолучение данных

6. Реальный сценарий: загрузка нескольких данных

❌ Неправильно (с launch)

var user: User? = null
var posts: List<Post>? = null

launch {
    user = api.getUser()
    posts = api.getPosts()
}

// ❌ user и posts ещё null!
println("User: ${user?.name}") // null
println("Posts: ${posts?.size}") // null

✅ Правильно (с async)

viewModelScope.launch {
    val userAsync = async { api.getUser() }
    val postsAsync = async { api.getPosts() }
    
    val user = userAsync.await()      // ✅ Получим результат
    val posts = postsAsync.await()    // ✅ Получим результат
    
    updateUI(user, posts)
}

// Это распараллеливается — обе загружаются одновременно!

7. launch с присваиванием — когда это работает

Это работает, но только если используешь reactive паттерн:

class ViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
    
    fun load(id: Int) {
        viewModelScope.launch {  // ← launch тут OK
            _user.value = api.getUser(id)  // Результат в LiveData
        }
    }
}

// View наблюдает за изменениями
viewModel.user.observe(this) { user ->
    // Автоматически обновляется когда load() завершится
    updateUI(user)
}

8. Job.join() vs await() — дождаться завершения

Job.join() — ждёт завершения, но без результата:

val job = launch {
    delay(1000)
    println("Done")
}

job.join()  // Ждём, пока launch завершится
println("Job finished")

Deferred.await() — ждёт завершения И возвращает результат:

val deferred = async {
    delay(1000)
    "Result"
}

val result = deferred.await()  // Ждём И получаем результат
println(result)  // "Result"

9. Обработка ошибок в launch

В launch ошибки не выбрасываются автоматически:

// ❌ Exception будет проигнорирован
launch {
    throw Exception("Error")
}
println("Это выпечатается")

// ✅ Нужен try-catch
launch {
    try {
        throw Exception("Error")
    } catch (e: Exception) {
        handleError(e)
    }
}

В async ошибка выбросится на await():

// ✅ Exception выбросится на await()
try {
    val result = async {
        throw Exception("Error")
    }.await()
} catch (e: Exception) {
    handleError(e)
}

10. Правило: используй launch или async?

Нужен результат? → async
Не нужен результат? → launch

Нужно ждать? → async.await() или job.join()
Не нужно ждать? → launch (запусти и забудь)

Нужна reactive реакция на результат? → launch + LiveData/StateFlow

Итог

  • launch не возвращает результат — это для побочных эффектов
  • async возвращает результат через await() (возвращает Deferred<T>)
  • Не используй переменные для передачи результата из launch
  • Не блокируй главный поток с Thread.sleep()
  • Для результатов используй async, LiveData, StateFlow или callback'и
  • Правило: launch = побочные эффекты, async = получить значение