Search code examples
iosswiftcombine

How to wrap an async function inside AnyPublisher?


Lets say I want to create an asynchronous function that calls some random API and returns a random Int. I want wrap it using a future

func createFuture() -> Future<Int, Never> {
  return Future { promise in
    promise(.success(Int.random(1...10))
  }
}

This would return me the same output everytime. Instead i want to return AnyPublisher.

func createAnyPublisher() -> AnyPublisher<Int, Never> {  //This is invalid
    return AnyPublisher<Int, Never> { seed in
        seed.success(Int.random(in: 1...10))
    }
}

A better example:

func guessNumber(num: Int) -> AnyPublisher<Bool, Never> {
    asyncRandomNumber { winner in
        if num == winner {
            // return true
        } else {
            // return false
        }
    }
}

private func asyncRandomNumber(completion: (Int) -> Void) {
    completion(Int.random(in: 1...10))
}

How do you wrap that asyncRandomNumber ?


Solution

  • Future caches its result at initialisation, that's why you always see the same result. You can use Just instead if you want each subscriber to receive a different random number.

    func randomIntPublisher() -> AnyPublisher<Int, Never> {
        Just(Int.random(in: 1...10)).eraseToAnyPublisher()
    }
    

    If you want to wrap an async function in a Publisher, you can use Future, just make sure you call your function each time before creating a new subscription - this will make sure that you create a new Future and hence the result won't be cached and shared between the different subscribers.

    func asyncRandomNumber(completion: @escaping (Int) -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            completion(Int.random(in: 1...100))
        }
    }
    
    func asyncRandomNumber() -> AnyPublisher<Int, Never> {
        Future { promise in
            asyncRandomNumber(completion: { num in
                promise(.success(num))
            })
        }.eraseToAnyPublisher()
    }
    
    asyncRandomNumber().sink(receiveValue: { print("First subscription received \($0)") }).store(in: &subscriptions)
    asyncRandomNumber().sink(receiveValue: { print("Second subscription received \($0)") }).store(in: &subscriptions)