kotlin.js.Promise has function then with this declaration:
open fun <S> then(
onFulfilled: (T) -> S,
onRejected: (Throwable) -> S = definedExternally
): Promise<S>
I have two functions a() and b(). They both return a Promise<Int>. (They represent some requests to the server.) I need to combine them and create a new function like:
fun c(): Promise<Int> {
a().then({
b()
})
}
But it is not possible, because return type is Promise<Promise<Int>> and not Promise<Int>.
I think this is possible in Javascript. How can I chain promises in Kotlin?
you need an additional Promise for that, for example:
fun c(): Promise<Int> {
return Promise({ resolve, reject ->
a().then({
b().then(resolve, reject);
});
})
}
the code above also can simplified by using single-expression function as below:
fun c() = Promise({ resolve, reject ->
a().then({
b().then(resolve, reject);
});
});
fun c(): Promise<Int> {
return a().then ({
b().unsafeCast<Int>()
//Result of b is Promise<Int>, not Int, but then has bad type declaration
})
}
Related
I have 2 questions:
What difference between 'bind(onNext:' and 'subscribe(onNext:'?
struct Info {
var index: Int?
var data: String?
}
let infoData: BehaviorRelay<Info> = BehaviorRelay<Info>(value: Info())
var osInfo: Observable<String> { return self.infoData.map({ return $0.data }).distinctUntilChanged() }
osInfo.bind { (target) in
print("bind!")
}.disposed(by: self.disposeBag)
osInfo.subscribe { (target) in
print("subscribe!")
}
.disposed(by: self.disposeBag)
a has no asObservable(), but well executable. What is difference a and b?
a. var osInfo: Observable<String> { return self.infoData.map({ return $0.data }).distinctUntilChanged() }
b. var osInfo: Observable<String> { return self.infoData.asObservable().map({ return $0.data }).distinctUntilChanged() }
What difference between 'bind(onNext:' and 'subscribe(onNext:'?
If we check out implementation of bind(...) we found that it does nothing else but just uses subscribe(...) underhood and crashes in Debug with error:
/**
Subscribes an element handler to an observable sequence.
In case error occurs in debug mode, `fatalError` will be raised.
In case error occurs in release mode, `error` will be logged.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func bind(onNext: #escaping (E) -> Void) -> Disposable {
return subscribe(onNext: onNext, onError: { error in
rxFatalErrorInDebug("Binding error: \(error)")
})
}
By using bind(onNext) you can express that stream should never emit error and you interested only in item events.
So you should use subscribe(onNext:...) when you interested in error / complete / disposed events and bind(onNext...) otherwise. But since it is part of RxCocoa and not RxSwift I usually use subscribe everywhere.
a has no asObservable(), but well executable. What is difference a and b?
map(...) is function declared on ObservableType and returning new Observable
Let's start from ObservableType.
ObservableType is protocol that require only one method: subscribe(...), this allow him to create default implementation of func asObservable().
For you it means that you can create Observable from any type that conform to ObservableType.
/// Represents a push style sequence.
public protocol ObservableType : ObservableConvertibleType {
func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E
}
extension ObservableType {
/// Default implementation of converting `ObservableType` to `Observable`.
public func asObservable() -> Observable<E> {
// temporary workaround
//return Observable.create(subscribe: self.subscribe)
return Observable.create { o in
return self.subscribe(o)
}
}
}
So each time you call asObservable() underhood RxSwift just creates new Observable wrapper around your stream.
And if you check source of BehaviourRelay you will find that it conform to ObservableType as well. So you can create Observable from it anytime:
public final class BehaviorRelay<Element>: ObservableType { ... }
Now lets check map function:
extension ObservableType {
/**
Projects each element of an observable sequence into a new form.
- seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html)
- parameter transform: A transform function to apply to each source element.
- returns: An observable sequence whose elements are the result of invoking the transform function on each element of source.
*/
public func map<R>(_ transform: #escaping (E) throws -> R)
-> Observable<R> {
return self.asObservable().composeMap(transform)
}
}
As expected map just call asObservable() inside and operate on new Observable.
If we "unwrap" map call we will get:
var osInfoA: Observable<String> {
return infoData
.asObservable()
.composeMap { $0.data }
.distinctUntilChanged()
}
var osInfoB: Observable<String> {
return infoData
.asObservable()
.asObservable()
.composeMap { $0.data }
.distinctUntilChanged()
}
Sure it will not compile since composeMap is internal function but you got main idea.
Calling asObservable before other operators is redundant (most operators defined on ObservableType) and just add small overhead.
I am appending a Flux with a flatMap, but if I add additional flatMaps, only the last one gets returned.
// Here is an example of the Mono function
private Mono<MyType> appendFirstMono(Group group) {
return Mono.just(group)
.map(MyType::new)
.flatMap(g -> functionZ(group)
.map(g::setField));
}
//This works as expected
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(this::appendFirstMono);
}
//This does not and only returns the last mono (3rd)
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(this::appendFirstMono)
.flatMap(this::appendSecondMono)
.flatMap(this::appendThirdMono);
}
//I've attempted to fix with this... Doesn't work as expected.
public Flux<MyType> function1() {
return returnData(id)
.thenMany(service.getData(id))
.flatMap(x -> {
return Flux.merge(
appendFirstMono(x),
appendSecondMono(x),
appendThirdMono(x)
);
});
}
I need to process each Mono function on the flux but I can't seem to get each to execute and return properly.
You can try concat to process the mono one by one check out my example
Flux.concat(getMono(0),getMono(1),getMono(2))
.map(integer -> {
System.out.println(integer);
return integer;
})
.subscribe();
}
private Mono<Integer> getMono(Integer a) {
return Mono.just(a)
;
}
this will print 0,1,2
I'm trying to figure out how to implement reconnect to observable after a transient failure, to continue from a last emitted value.
Assume I have the following method:
interface MyQuery {
fromId: number;
toId: number;
}
interface MyItem {
id: number;
val: string;
}
function observeUnstable(query: MyQuery): Observable<MyItem>;
The method observableUnstable lets to subscribe to a stream which emits values and may emit the following error in case of intermittent connection failure:
class DisconnectedError extends Error;
I want to compose a new observable which would wrap the original observable above and have transparent resubscribe from the position at which the previous subscription has failed.
The data types are going to be opaque, so I would want to make the reconnection logic generic, probably as an operator which would accept a high order selector function:
let startQuery = { fromId: 1, toId: 10 };
let reconnectable = observeUnstable(startQuery)
.lift(new ReconnectOperator<MyItem>((err, lastValue?) => {
if (err instanceof DisconnectedError) {
// This error indicates that we've been disconnected,
// resubscribing from the place we have stopped
let continueQuery = {
fromId: lastValue ? lastValue.id + 1 : startQuery.fromId,
toId: startQuery.toId
};
return observeUnstable(continueQuery);
} else {
// Rethrowing error we don't expect
throw err;
}
}));
Here are my ReconnectOperator and ReconnectSubscriber:
class ReconnectOperator<T> implements Operator<T, T> {
constructor(private handler: (err: any, lastValue?: T) => Observable<T>) {
}
call(subscriber: Subscriber<T>, source: any): any {
return source.subscribe(new ReconnectSubscriber(subscriber, this.handler));
}
}
class ReconnectSubscriber<T> extends Subscriber<T> {
private lastValue?: T;
constructor(destination: Subscriber<T>, private handler: (err: any, lastValue?: T) => Observable<T>) {
super(destination);
}
protected _next(value: T) {
this.lastValue = value;
super._next(value);
}
error(err: any) {
if (!this.isStopped) {
let result: Observable<T>;
try {
result = this.handler(err, this.lastValue);
} catch (err2) {
super.error(err2);
return;
}
// TODO: ???
result.subscribe(this._unsubscribeAndRecycle());
// this._unsubscribeAndRecycle();
//this.source.subscribe(result);
//this.add(subscribeToResult(this, result));
}
}
}
This subscriber is very similar to CatchSubscriber with only one difference is that CatchSubscriber returns original observable in selector method, in my case I want to return last value so the selector could use it to compose a brand new observable rather than reusing the original one.
But I messed with resubscribe logic somehow so the resulting observable never returns complete for small amount of test values, and crashes with stack overflow for bigger amount of test values.
Also, my idea here is to implement a new operator but if it's possible to implement it in a single method just using composition of existing operators, in a generic way, that would be even better :)
Example of an alternative method but without operator:
function observeStable<T, Q>(
query: Q,
continueQueryFunc: (query: Q, lastValue?: T) => Observable<T>
): Observable<T> {
return observeUnstable<T>(query).catch((err, ???) =>
if (err instanceof DisconnectedError) {
let lastValue = ???
let continueQuery = continueQueryFunc(query, lastValue);
return observeUnstable(continueQuery);
} else {
throw err;
}
);
}
I'm having the following simplified handler function (Spring WebFlux and the functional API using Kotlin). However, I need a hint how to detect an empty Flux and then use noContent() for 404, when the Flux is empty.
fun findByLastname(request: ServerRequest): Mono<ServerResponse> {
val lastnameOpt = request.queryParam("lastname")
val customerFlux = if (lastnameOpt.isPresent) {
service.findByLastname(lastnameOpt.get())
} else {
service.findAll()
}
// How can I detect an empty Flux and then invoke noContent() ?
return ok().body(customerFlux, Customer::class.java)
}
From a Mono:
return customerMono
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))
.switchIfEmpty(notFound().build());
From a Flux:
return customerFlux
.collectList()
.flatMap(l -> {
if(l.isEmpty()) {
return notFound().build();
}
else {
return ok().body(BodyInserters.fromObject(l)));
}
});
Note that collectList buffers data in memory, so this might not be the best choice for big lists. There might be a better way to solve this.
Use Flux.hasElements() : Mono<Boolean> function:
return customerFlux.hasElements()
.flatMap {
if (it) ok().body(customerFlux)
else noContent().build()
}
In addition to the solution of Brian, if you are not want to do an empty check of the list all the time, you could create a extension function:
fun <R> Flux<R>.collectListOrEmpty(): Mono<List<R>> = this.collectList().flatMap {
val result = if (it.isEmpty()) {
Mono.empty()
} else {
Mono.just(it)
}
result
}
And call it like you do it for the Mono:
return customerFlux().collectListOrEmpty()
.switchIfEmpty(notFound().build())
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))
I'm not sure why no one is talking about using the hasElements() function of Flux.java which would return a Mono.
Suppose you have a branch in your promise chain that could either return nothing or an AnyObject promise. What would you specify as the return type of the 'then' closure? For example:
func sample() -> Promise<AnyObject> {
return Promise { fulfill, reject in
fulfill(1)
}
.then { _ -> Void in
if false {
return Promise { fulfill, reject in
fulfill(0)
}
}
}
}
If I put Void as the return type for the 'then' closure I get a seg fault; if I put Promise as return type then I get an error:
missing return in a closure expected to return Promise<AnyObject>
Any suggestions?
Thanks
Based on the code sample, I see no reason to return an AnyObject. If you want to optionally return Void or an Object, then make a promise that contains an optional.
func sample() -> Promise<AnyObject?> {
return Promise { fulfill, reject in
functionForGettingObjectWithCallback() { result: AnyObject? in
fulfill(result)
}
}
}