Skip to content

Files

Latest commit

 

History

History
75 lines (59 loc) · 4.39 KB

pattern-retry.adoc

File metadata and controls

75 lines (59 loc) · 4.39 KB

Retrying in the event of a temporary failure

Goal
  • The retry operator can be included in a pipeline to retry a subscription when a .failure completion occurs.

References
See also
Code and explanation

When requesting data from a dataTaskPublisher, the request may fail. In that case you will receive a .failure completion with an error. When it fails, the retry operator will let you retry that same request for a set number of attempts. The retry operator passes through the resulting values when the publisher does not send a .failure completion. retry only reacts within a combine pipeline when a .failure completion is sent.

When retry receives a .failure completion, the way it retries is by recreating the subscription to the operator or publisher to which it was chained.

The retry operator is commonly desired when attempting to request network resources with an unstable connection, or other situations where the request might succeed if the request happens again. If the number of retries specified all fail, then the .failure completion is passed down to the subscriber.

In our example below, we are using retry in combination with a delay operator. Our use of the delay operator puts a small random delay before the next request. This spaces out the retry attempts, so that the retries do not happen in quick succession.

This example also includes the use of the tryMap operator to more fully inspect any URLResponse returned from the dataTaskPublisher. Any response from the server is encapsulated by URLSession, and passed forward as a valid response. URLSession does not treat a 404 Not Found http response as an error response, nor any of the 50x error codes. Using tryMap lets us inspect the response code that was sent, and verify that it was a 200 response code. In this example, if the response code is anything but a 200 response, it throws an exception - which in turn causes the tryMap operator to pass down a .failure completion rather than data. This example sets the tryMap after the retry operator so that retry will only re-attempt the request when the site didn’t respond.

let remoteDataPublisher = urlSession.dataTaskPublisher(for: self.URL!)
    .delay(for: DispatchQueue.SchedulerTimeType.Stride(integerLiteral: Int.random(in: 1..<5)), scheduler: backgroundQueue) (1)
    .retry(3) (2)
    .tryMap { data, response -> Data in (3)
        guard let httpResponse = response as? HTTPURLResponse,
            httpResponse.statusCode == 200 else {
                throw TestFailureCondition.invalidServerResponse
        }
        return data
    }
    .decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder())
    .subscribe(on: backgroundQueue)
    .eraseToAnyPublisher()
  1. The delay operator will hold the results flowing through the pipeline for a short duration, in this case for a random selection of 1 to 5 seconds. By adding delay here in the pipeline, it will always occur, even if the original request is successful.

  2. Retry is specified as trying 3 times. This will result in a total of four attempts if each fails - the original request and 3 additional attempts.

  3. tryMap is being used to inspect the data result from dataTaskPublisher and return a .failure completion if the response from the server is valid, but not a 200 HTTP response code.

Warning

When using the retry operator with URLSession.dataTaskPublisher, verify that the URL you are requesting isn’t going to have negative side effects if requested repeatedly or with a retry. Ideally such requests are be expected to be idempotent. If they are not, the retry operator may make multiple requests, with very unexpected side effects.