Что такое Future/Promise?ios-9

Основная концепция

Future и Promise — это два взаимосвязанных паттерна для работы с асинхронными операциями. Они представляют собой абстракцию над значением, которое может быть доступно сейчас или в будущем.

Аналогия из реального мира:

  • Promise — это квитанция, которую вы получаете при заказе еды
  • Future — это фактическая еда, которую вы получите позже

Детальное определение

Promise

  • Это объект, который создает асинхронную операцию
  • Позволяет завершить операцию (успешно или с ошибкой)
  • Обычно возвращает Future для подписки

Future

  • Это объект, который представляет результат асинхронной операции
  • Позволяет подписаться на результат
  • Может находиться в одном из состояний:
    • Pending (ожидание)
    • Fulfilled (успешно завершено)
    • Rejected (завершено с ошибкой)

Реализация в Swift

Пример с Combine

import Combine

func fetchUser(id: Int) -> Future<User, Error> {
    return Future { promise in
        URLSession.shared.dataTask(with: userURL(for: id)) { data, _, error in
            if let error = error {
                promise(.failure(error))
            } else if let data = data {
                do {
                    let user = try JSONDecoder().decode(User.self, from: data)
                    promise(.success(user))
                } catch {
                    promise(.failure(error))
                }
            }
        }.resume()
    }
}

// Использование
var cancellables = Set<AnyCancellable>()

fetchUser(id: 123)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished: break
        case .failure(let error): print("Error: \(error)")
        }
    }, receiveValue: { user in
        print("Received user: \(user)")
    })
    .store(in: &cancellables)

Пример с PromiseKit

import PromiseKit

func fetchAvatar(userId: Int) -> Promise<UIImage> {
    return Promise { seal in
        firstly {
            fetchUser(id: userId)
        }.then { user in
            URLSession.shared.dataTask(.promise, with: user.avatarURL)
        }.done { data in
            if let image = UIImage(data: data) {
                seal.fulfill(image)
            } else {
                seal.reject(AvatarError.invalidImage)
            }
        }.catch { error in
            seal.reject(error)
        }
    }
}

Ключевые преимущества

  1. Избегание callback hell: Плохо:

    loadData { data in
        parse(data) { result in
            save(result) { success in
                // Уже 3 уровня вложенности
            }
        }
    }
    

    Хорошо:

    loadData()
        .flatMap(parse)
        .flatMap(save)
        .sink { /* единый обработчик */ }
    
  2. Композиция операций:

    • Последовательные вызовы (flatMap)
    • Параллельные вызовы (zip, combineLatest)
    • Обработка ошибок (catch, retry)
  3. Читаемость кода:

    • Линейный поток выполнения
    • Явная обработка ошибок
    • Легко добавлять промежуточные шаги

Разница между Future и Promise

ХарактеристикаPromiseFuture
Ответственность Создание результата Подписка на результат
Интерфейс Завершение операции Чтение результата
Мутабельность Мутабельный (один раз) Иммутабельный
Аналог Producer Consumer

Практическое применение в iOS

  1. Сетевые запросы:

    Future<Data, Error> { promise in
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                promise(.failure(error))
            } else if let data = data {
                promise(.success(data))
            }
        }.resume()
    }
    
  2. Базы данных:

    func saveToCoreData(object: Model) -> Future<Void, Error> {
        Future { promise in
            context.perform {
                do {
                    try context.save()
                    promise(.success(()))
                } catch {
                    promise(.failure(error))
                }
            }
        }
    }
    
  3. UserDefaults/Keychain:

    func loadSettings() -> Future<Settings, Error> {
        Future { promise in
            // Асинхронная загрузка
        }
    }
    

Ошибки и их обработка

Основные подходы:

  1. Преобразование ошибок:

    fetchUser(id: 123)
        .mapError { networkError in
            return MyAppError.network(networkError)
        }
    
  2. Повторные попытки:

    fetchUser(id: 123)
        .retry(3)
        .sink { /* ... */ }
    
  3. Fallback значения:

    fetchUser(id: 123)
        .replaceError(with: User.anonymous)
        .sink { /* ... */ }
    

Комбинирование операций

Последовательное выполнение:

fetchUser(id: 123)
    .flatMap { user in
        fetchAvatar(for: user)
    }
    .sink { avatar in
        // Обработка аватара
    }

Параллельное выполнение:

Publishers.Zip(
    fetchUserProfile(),
    fetchUserSettings()
).sink { profile, settings in
    // Оба значения получены
}

Память и отмена операций

Важно правильно управлять жизненным циклом:

class ViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        fetchData()
            .sink { data in
                // Обработка
            }
            .store(in: &cancellables)  // Автоматическая отмена при deinit
    }
}

Резюмируем

Future/Promise — это мощные абстракции для работы с асинхронным кодом в iOS, которые:

  1. Упрощают асинхронные workflows
  2. Улучшают читаемость кода
  3. Предоставляют богатые возможности композиции

Основные реализации в iOS:

  • Combine.Future (нативно с iOS 13)
  • PromiseKit (популярная сторонняя библиотека)
  • RxSwift.Single (альтернативный подход)

Ключевые моменты:

  • Promise создает операцию, Future представляет результат
  • Отлично подходят для сетевых запросов, работы с БД
  • Позволяют избегать "callback hell"
  • Требуют аккуратного управления памятью

Использование Future/Promise делает асинхронный код более предсказуемым и поддерживаемым в iOS приложениях.