Rodrigo Ruiz
Rodrigo Ruiz

Reputation: 4355

RXSwift - takeUntil canceling before next event

Following a similar example to question 39 here: http://reactivex.io/learnrx/

I'm trying to transform a method call search(query: String) into a sequence of those calls. They way I'm achieving this is by creating a Variable which I update with the query value every time the search(query: String) method is called.

Then I have this in my init():

_ = queryVariable.asObservable().flatMap({ query -> Observable<[JSON]> in
    return self.facebookSearch(query).takeUntil(self.queryVariable.asObservable())
}).subscribeNext({ result in
    if let name = result[0]["name"].string {
        print(name)
    } else {
        print("problem")
    }
})

If I type "ABC", my search(query: String) method will be called 3 times with "A", "AB", "ABC". That would be mapped to seq(["A", "AB", "ABC"]) with queryVariable.asObservable(). Then I'm mapping it to Facebook searches (searching people by their names on Facebook). And with subscribeNext I print the name. If I don't use the takeUntil, it works as I'd expect, I get 3 sets of results, one for each of my queries("A", "AB", "ABC").

But if I type fast (before Facebook has time to respond to the request), I'd want only one result, for the query "ABC". That's why I added the takeUntil. With it I'd expect the facebookSearch(query: String) call to be ignored when the next query comes in, but it is being canceled for the current query, so with this takeUntil I end up printing nothing.

Is this a known issue or am I doing something wrong?

Upvotes: 2

Views: 3381

Answers (1)

joern
joern

Reputation: 27620

I used your code and found two solutions to your problem:

1. Use flatMapLatest

You can just use flatMapLatest instead of flatMap and takeUntil. flatMapLatest only returns the results of the latest search request and cancels all older requests that have not returned yet:

_ = queryVariable.asObservable()
    .flatMapLatest { query -> Observable<String> in
        return self.facebookSearch(query)
    }
    .subscribeNext {
        print($0)
    }

2. Use share

To make your approach work you have to share the events of your queryVariable Observable when you also use it for takeUntil:

let queryObservable = queryVariable.asObservable().share()

_ = queryObservable
    .flatMap { query -> Observable<String> in
        return self.facebookSearch(query).takeUntil(queryObservable)
    }
    .subscribeNext {
        print($0)
    }

If you do not share the events, the searchQuery.asObservable() in takeUntil creates its own (duplicate) sequence. Then when a new value is set on the searchQuery Variable it immediately fires a Next event in the takeUntil() sequence and that cancels the facebookSearch results.

When you use share() the sequence in takeUntil is observing the same event as the other sequence and in that case the takeUntil sequence handles the Next event after the facebookSearch has returned a response.

IMHO the first way (flatMapLatest) is the preferred way on how to handle this scenario.

Upvotes: 3

Related Questions