Объединить рамки повторить после задержки? - PullRequest
3 голосов
/ 10 марта 2020

Я вижу, как напрямую использовать .retry для повторной подписки после ошибки, например:

    URLSession.shared.dataTaskPublisher(for:url)
        .retry(3)

Но это кажется очень простым. Что если я подумаю, что эта ошибка может go исчезнуть, если я подожду некоторое время? Я мог бы вставить оператор .delay, но тогда задержка срабатывает, даже если ошибки нет. И, кажется, нет способа применить оператор условно (то есть, только когда есть ошибка).

Я вижу, как можно обойти это, написав оператор RetryWithDelay с нуля, и, действительно, такой Оператор был написан третьими лицами. Но есть ли способ сказать «задержка, если есть ошибка», просто используя операторы, которые нам даны?

Я думал, что я мог бы использовать .catch, потому что его функция выполняется только при наличии ошибка. Но функция должна возвращать издателя, и какого издателя мы будем использовать? Если мы вернем somePublisher.delay(...) с последующим .retry, мы бы применили .retry не к тому издателю, не так ли?

Ответы [ 2 ]

3 голосов
/ 11 марта 2020

Использование .catch действительно ответ. Мы просто делаем ссылку на издателя задачи данных и используем эту ссылку в качестве заголовка обоих конвейеров - внешнего конвейера, который выполняет начальное сетевое взаимодействие, и внутреннего конвейера, создаваемого функцией .catch.

Давайте начнем Создав задачу издателя данных и остановив :

let pub = URLSession.shared.dataTaskPublisher(for: url).share()

Теперь я могу сформировать заголовок конвейера:

let head = pub.catch {_ in pub.delay(for: 3, scheduler: DispatchQueue.main)}
    .retry(3)

Это должно сделать это! head теперь представляет собой конвейер, который вставляет оператор задержки только на случай, если произошла ошибка. Затем мы можем приступить к формированию остальной части конвейера, основываясь на head.

Заметьте, что мы действительно меняем издателей; если происходит сбой, и запускается функция catch, то pub, который находится перед .delay, становится издателем, заменяя pub, с которого мы начинали. Однако это один и тот же объект (потому что я сказал share), так что это различие без разницы.

3 голосов
/ 11 марта 2020

Это была топи c беседы на Использование комбината репо проекта некоторое время назад - весь поток: https://github.com/heckj/swiftui-notes/issues/164.

Длинный Короче говоря, мы сделали пример, который, я думаю, делает то, что вы хотите, хотя он использует catch:

let resultPublisher = upstreamPublisher.catch { error -> AnyPublisher<String, Error> in
    return Publishers.Delay(upstream: upstreamPublisher,
                            interval: 3,
                            tolerance: 1,
                            scheduler: DispatchQueue.global())
    // moving retry into this block reduces the number of duplicate requests
    // In effect, there's the original request, and the `retry(2)` here will operate
    // two additional retries on the otherwise one-shot publisher that is initiated with
    // the `Publishers.Delay()` just above. Just starting this publisher with delay makes
    // an additional request, so the total number of requests ends up being 4 (assuming all
    // fail). However, no delay is introduced in this sequence if the original request
    // is successful.
    .retry(2)
    .eraseToAnyPublisher()
}

Это ссылка на шаблон повторения , который я имею в книге / онлайн , который, по сути, является тем, что вы описываете (но не то, о чем вы спрашивали).

человек, с которым я переписывался по вопросу , предоставил вариант в этой теме в качестве расширения это также может быть интересно:

extension Publisher {
  func retryWithDelay<T, E>()
    -> Publishers.Catch<Self, AnyPublisher<T, E>> where T == Self.Output, E == Self.Failure
  {
    return self.catch { error -> AnyPublisher<T, E> in
      return Publishers.Delay(
        upstream: self,
        interval: 3,
        tolerance: 1,
        scheduler: DispatchQueue.global()).retry(2).eraseToAnyPublisher()
    }
  }
}
...