Home > Software design >  How to retry a request with different input?
How to retry a request with different input?

Time:10-23

Goal to make retry with different input data.

func generateRandomName() -> Int { ... }

checkIfNameIsAvailable(generateRandomName())
   .retry(10) // <- Makes 10 attempts with same link
   .sink(
       receiveCompletion: { completion in
            },
            receiveValue: { value in
                // Do things
            }
          )
          .store(in: &cancellables)

How can I modify retry to retry with different upstream (request different query parameter) and 10 attempts?

CodePudding user response:

You can't use retry for that. That's not what retry does.

Here's a different strategy:

  1. Publish a ten-element sequence.
  2. Transform each of those elements into a query.
  3. Flatmap each query into a publisher of the query result.
  4. Take a one-element prefix of the flatmap.

Thus:

func generateRandomName(seed: Int) -> String {
    return "name\(seed)"
}

struct NameTakenError: Error { }

func availableNamePublisher(name: String) -> AnyPublisher<String, Error> {
    print("checking availability of \(name)")
    if
        let digit = name.last?.wholeNumberValue,
        digit > 3
    {
        return Result.success(name).publisher.eraseToAnyPublisher()
    } else {
        return Result.failure(NameTakenError()).publisher.eraseToAnyPublisher()
    }
}

let ticket = (0 ..< 10).publisher
    .map { i in generateRandomName(seed: i) }
    .flatMap(maxPublishers: .max(1)) { name in
        availableNamePublisher(name: name)
            .catch { _ in Empty() }
    }
    .prefix(1)
    .sink(
        receiveCompletion: { print("completion: \($0)") },
        receiveValue: { print("available name: \($0)") }
    )

Output:

checking availability of name0
checking availability of name1
checking availability of name2
checking availability of name3
checking availability of name4
available name: name4
completion: finished

CodePudding user response:

You could use some higher order functions to achieve the goal.

Like this one below:

func retry<P: Publisher>(_ times: Int, _ publisherBuilder: @escaping () -> P) -> AnyPublisher<P.Output, P.Failure> {
    if times <= 1 {
        return publisherBuilder().eraseToAnyPublisher()
    } else {
        return publisherBuilder()
            .catch { _ in retry(times-1, publisherBuilder) }
            .eraseToAnyPublisher()
    }
}

The function takes as arguments a number of retries, and a publisher builder closure. This gives you flexibility when it comes to generating new publishers on the retrial path, as the closure will be invoked every time the retrial is made:

retry(10) { checkIfNameIsAvailable(generateRandomName()) }
   .sink(
       receiveCompletion: { completion in
            },
            receiveValue: { value in
                // Do things
            }
          )
          .store(in: &cancellables)
  • Related