Создать аналог ошибки catch/replace, который не завершает публикацию?

#swift #combine

Вопрос:

Я борюсь с обработкой ошибок в Combine и нахожу очень нелогичным, что обнаружение или замена ошибки завершает и завершает издателя. Например, это завершает публикацию, даже если я обработал исключение:

 Just([1, 2, 3, NaN, 5, 6])
   .tryMap { _ in throw DummyError() }
   .catch { _ in Just(4) } // or .replaceError(with: 4)
   .sink { print($0) } // <-- ends at 4 and ignores 5, 6, and anything ever again
 

Если издатель был подписан на какое-либо изменение системы, первая ошибка приведет к отключению моего приложения до тех пор, пока пользователь не перезапустит приложение. Я хотел бы создать catch аналог, который НЕ заканчивает издателя. Я знаю flatMap , что для этого используется, но это создает совершенно другое измерение сложности, такое как инвертирование внутренних/внешних издателей, бесконечное дублирование издателей, обратное давление и т. Д.

Есть ли способ сделать что-то подобное:

 Just([1, 2, 3, NaN, 5, 6])
   .tryMap { _ in throw DummyError() }
   .ignoreError { error in
        log("Error occurred: (error)")
   }
   .sink { print($0) } // 1, 2, 3, 5, 6
 

Как я могу инкапсулировать это намерение в простую пользовательскую команду цепочки ignoreError и заставить моих издателей жить дальше?

Комментарии:

1. А как насчет .replaceError вместо .catch этого ?

2. К сожалению, .replaceError также завершает и заканчивает издатель: developer.apple.com/documentation/combine/fail/. … Но также я не хочу выдавать значение по умолчанию, а просто регистрируюсь и ничего не выдаю.

3. Для этого вам нужно использовать flatMap, извините. Я не думаю, что это сопряжено с такой большой сложностью, как вы, кажется, думаете, но это субъективно.

4. Можете ли вы, пожалуйста, добавить пример в ответ, чтобы я мог понять больше? Вероятно, вы правы, и мне нужно преодолеть ментальное препятствие

5. @TruMan1, вот способ подумать об издателях, которые терпят неудачу: задача издателя — выдавать значения, и это то, на что вы подписываетесь; и он делает это до тех пор, пока не потерпит неудачу- т. Е. Весь издатель терпит неудачу. Вам либо нужно повторно подписаться — в основном начать заново, что хорошо работает при временных сетевых ошибках, либо вам нужно изолировать отказавшего издателя, как я показал flatmap .

Ответ №1:

Ошибка при завершении конвейера является частью контракта, по которому работают издатели и подписчики. И в этом есть смысл. Если вышестоящий издатель выдает ошибку и не знает, как восстановиться самостоятельно, это в основном сделано. Это мало чем отличается от функции, выдающей ошибку.

В вашем примере tryMap выдает ошибку, и хотя нисходящий replaceError поток может преобразовать ошибку для своего нисходящего потока, он не ожидает больше значений от своего восходящего потока, поэтому он тоже завершается.

flatMap защищает остальную часть конвейера от издателя, выдающего ошибки. Другими словами, восходящий поток flatMap не выдает ошибку и flatMap сам по себе не выдает ошибку, поскольку возвращенный внутренний издатель (конвейер с Just / tryMap / catch ) обрабатывает ошибку.

 Just([1, 2, 3, 5, 6])
   .flatMap {
      Just($0)
        .tryMap { _ in throw DummyError() }
        .catch { _ in Just(4) } 
   }
   .sink { print($0) }