Skip to content

Instantly share code, notes, and snippets.

@danielt1263
Last active June 20, 2022 08:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielt1263/a023a5bc7c583258e101a5024b6f8bc5 to your computer and use it in GitHub Desktop.
Save danielt1263/a023a5bc7c583258e101a5024b6f8bc5 to your computer and use it in GitHub Desktop.
//
// WithNextFrom.swift
//
// Created by Daniel Tartaglia on 7/07/21.
// Copyright © 2021 Daniel Tartaglia. MIT License.
//
import RxSwift
extension ObservableType {
/// Emit the next element from the second Observable immediatly after an element from the source Observable emits. The most recent from the source and the next event from the second observable will be passed to the resultSelector for processing.
/// - Parameters:
/// - second: The second observable to use.
/// - resultSelector: A pure function that accepts the latest source event and the next second observable emission.
/// - Returns: An observable that emits the result of the selector.
func withNextFrom<Source: ObservableConvertibleType, ResultType>(_ second: Source, resultSelector: @escaping (Element, Source.Element) throws -> ResultType) -> Observable<ResultType> {
let shared = share()
return Observable.merge(
shared.map(Either.right),
second.asObservable().map(Either.left).take(until: shared.takeLast(1))
)
.scan((Element?.none, (Element, Source.Element)?.none)) { current, latest in
switch latest {
case let .left(value):
return current.0.map { (nil, ($0, value)) } ?? (nil, nil)
case let .right(value):
return (value, nil)
}
}
.compactMap { try $0.1.map { try resultSelector($0.0, $0.1) } }
}
/// Emit the next element from the second Observable immediatly after an element from the source Observable emits.
/// - Parameters:
/// - second: The second observable to use.
/// - Returns: An observable that emits the next value from `second` whenever the source emits.
func withNextFrom<Source: ObservableConvertibleType>(_ second: Source) -> Observable<Source.Element> {
let shared = share()
return Observable.merge(
shared.map(Either.right),
second.asObservable().map(Either.left).take(until: shared.takeLast(1))
)
.scan((Element?.none, (Element, Source.Element)?.none)) { current, latest in
switch latest {
case let .left(value):
return current.0.map { (nil, ($0, value)) } ?? (nil, nil)
case let .right(value):
return (value, nil)
}
}
.compactMap { $0.1?.1 }
}
}
enum Either<A, B> {
case left(A)
case right(B)
}
@tylerjames
Copy link

Is this in contrast to withLatestFrom which won't actually cause the second observable to emit an event if it hasn't done so already?

@danielt1263
Copy link
Author

Yes. withLatestFrom stores the last value emitted by the second observable and then emits it on demand from the source. This operator stores the last value emitted by the source, then emits it, along with the next value emitted by the second Observable. I've never needed it myself, but someone asked for it so I posted it here.

@tlb-xiongju
Copy link

This saved me! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment