google / promises

Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.
Apache License 2.0
3.8k stars 293 forks source link

What is the best way to retry a promise? #45

Closed hpbl closed 6 years ago

hpbl commented 6 years ago

On the readme is says that using promises makes it easier to retry asynchronous operations. I couldn't find a retry method or anything of the sort, so what would be the simplest way to retry an operation using promises?

Thanks.

shoumikhin commented 6 years ago

Hi Hilton,

I guess the docs meant recover. Do you need something else? Just provide a usage example and we're happy to consider adding a new convenience extension if needed.

hpbl commented 6 years ago

Hey @shoumikhin thanks for the quick response, but I don't think recover does what I need. My use case would be the following:

makeRequest()
    .then {  // process data and carry on }
    .catch { error in
        // Retry the request n times before accepting error
    }

I'd like to be able to easily retry the request n times before accepting that it failed and carrying on with the error handling.

rumnat commented 6 years ago

It would be great to have such extension like in Rx world. If a promise fails, it tries to restart it as many times as you put in parameters.

DazChong commented 6 years ago

Thanks to out-of-the-box await! I'm using it for recursive promise.

    // e.g: each page page 10 items & total of 7 pages
    func getItemsCountAt(page: Int) -> Promise<Int> {
        let items = page > 7 ? 0 : 10
        return Promise(items)
    }

    // this function keep scanning until page with no more items
    func getAllItemsCount() -> Promise<Int> {
        return Promise<Int>(on: .global()) { () -> Int in
            var page = 1
            var totalItems = 0

            var pageItems = 0
            repeat {
                pageItems = try await(getItemsCountAt(page: page))
                totalItems += pageItems
                page += 1
            } while pageItems > 0

            return totalItems
        }
    }

    // usage
    getAllItemsCount().then { print($0) }
shoumikhin commented 6 years ago

Hi Hilton, sorry for the late reply. We have a prototype of retry operator working and will ship it soon. Meanwhile, would appreciate your feedback on the following example of possible use-cases:

func fetch(_ url: URL) -> Promise<(Data?, URLResponse?)> {
  return wrap { URLSession.shared.dataTask(with: url, completionHandler: $0).resume() }
}

// 1 extra retry attempt on initial fail and 1 second delay before the attempt by default.
retry { fetch(url) }.then { print($0) }.catch { print($0) }

// Custom number of retry attempts, custom delay and a predicate to bail early.
retry(
  attempts: 10,
  delay: 5,
  condition: { error, attemptNumber in
    (error as NSError).code == URLError.notConnectedToInternet.rawValue
  }
) {
  fetch(url)
}.then { value in
  print(value)
}.catch { error in
  print(error)
}

Let us know if that looks good enough, or if you have any ideas on how to make it even better.

hpbl commented 6 years ago

@shoumikhin It looks great for me! The default values are awesome for when you don't need that much control over the retry. Can't wait to use it!

rumnat commented 6 years ago

It would be better to have it like this: myAsyncFunc(param) .retry(3) .then { value in

}.catch { error in

}

shoumikhin commented 6 years ago

Hi Anton, I'm afraid that won't be possible, because in our implementation a promise doesn't hold any work block associated with it, so it's not possible to execute it again retrospectively. We've intentionally designed promises that way to avoid extra retain cycles, so that the work block is owned solely by GCD most of the time, instead of a promise itself.

ghost commented 6 years ago

retry operator is now available in the latest release (1.2.3): https://github.com/google/promises/blob/master/g3doc/index.md#retry

Please let us know if you have any questions or feedback or run into any issues. Thank you!