rxjs - operator with both on-next and on-error callbacks? - javascript

I'm looking for the equivalent of promise.then(onNextCallback,onErrorCallback) in rxjs.
is there something like this?
pipe(concatMap(),catchError) alternatives is not what I'm looking for.

I also asked for this as well and ended up writing my own.
import * as RxJS from 'rxjs';
/**
* Like `promise.then(onFulfilled, onRejected)`. This is *not* the same as
* `map(onNext).catchError(onError)`, because this operator will only catch errors resulting from
* the original observable—not errors resulting from `onNext`.
*/
export const mapOrCatchError = <T, B>(
onNext: (value: T) => B,
onError: (value: unknown) => B,
): RxJS.OperatorFunction<T, B> => ob$ =>
new RxJS.Observable<B>(observer =>
ob$.subscribe({
next: t => {
let next: B;
try {
next = onNext(t);
} catch (error) {
observer.error(error);
return;
}
observer.next(next);
},
error: error => {
let next: B;
try {
next = onError(error);
} catch (newError) {
observer.error(newError);
return;
}
observer.next(next);
observer.complete();
},
complete: () => {
observer.complete();
},
}),
);
Tests:
import { marbles } from 'rxjs-marbles/jest';
import { mapOrCatchError } from '../operators';
describe('mapOrCatchError', () => {
it(
'should map',
marbles(m => {
const source$ = m.cold('--(a|)', { a: 1 });
const expected = ' --(b|)';
const actual$ = source$.pipe(
mapOrCatchError(
a => a + 1,
_error => 0,
),
);
m.expect(actual$).toBeObservable(expected, { b: 2 });
}),
);
it(
'should catch',
marbles(m => {
const source$ = m.cold('--#');
const expected = ' --(a|)';
const actual$ = source$.pipe(
mapOrCatchError(
a => a + 1,
_error => 0,
),
);
m.expect(actual$).toBeObservable(expected, { a: 0 });
}),
);
it(
'should error if error handler throws',
marbles(m => {
const source$ = m.cold('--#');
const expected = ' --#';
const error = new Error('foo');
const actual$ = source$.pipe(
mapOrCatchError(
a => a + 1,
_error => {
throw error;
},
),
);
m.expect(actual$).toBeObservable(expected, undefined, error);
}),
);
it(
'should not catch errors thrown by map function',
marbles(m => {
const source$ = m.cold('--(a|)');
const expected = ' --#';
const error = new Error('foo');
const actual$ = source$.pipe(
mapOrCatchError(
() => {
throw error;
},
_error => 'caught error',
),
);
m.expect(actual$).toBeObservable(expected, undefined, error);
}),
);
});

Indeed it is not of my knowledge any operator that would do what you need.
What I do suggest, though, it's to create your own.
function promiseLike<T>(
onNext: (data: T) => Observable<T>,
onError: (err: any) => Observable<any>
) {
type ErrorWrapper = {isError: boolean, err: any};
const isErrorWrapper = (err: any): err is ErrorWrapper => {
return err.isError && err.err !== undefined;
}
return function(source: Observable<T>): Observable<T> {
return source.pipe(
catchError((err) => of({isError: true, err})),
switchMap((data) => isErrorWrapper(data) ? onError(data.err) : onNext(data))
);
}
}
The above basically wraps the error from source observable and then we switchMap to decide if I run the onNext and onError. This way, for sure, the onError won't catch possible errors coming from the onNext.
Here are the example of usage:
function getResolvingPromise(): Promise<string> {
return Promise.resolve('SUCCESS');
}
function getErrorPromise(): Promise<string> {
return Promise.reject('onError');
}
// Example with success
from(getResolvingPromise()).pipe(
promiseLike(
(data) => of(`received ${data}`),
(err) => of(3)
)
).subscribe((d) => console.log('Ex1', d)) // Logs Ex1 received SUCCESS
// Example with error
from(getErrorPromise()).pipe(
promiseLike(
(data) => of(`received ${data}`),
(err) => of(3)
)
).subscribe((d) => console.log('Ex2', d)) // Logs Ex2 3
// Example with internal error 2
from(getResolvingPromise()).pipe(
promiseLike(
(data) => throwError('Valid token not returned'),
(err) => of(3)
),
catchError(() => of('catching internal error'))
).subscribe((d) => console.log('Ex3', d)) // Logs Ex3 catching internal error

What about something like this?
source$ is the source Observable and you can provide corresponding functions via tap operator inside the pipe.
source$.pipe(
tap({ next: onNextCallback, error: onErrorCallback })
)

Is .toPromise() what are you looking for ?
https://www.learnrxjs.io/learn-rxjs/operators/utility/topromise
Then you can do your .then(nextCallback) .catch(errorCallback)

Related

How to make two api calls using Promise.all within Angular9?

I making an api call using Promise.all as below:
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is working fine.
Now I want to make another api call after the successful completion of this.serviceB.retry(oretry).
The second api is this.serviceB.createDbEntry(sentry) and sentry looks as below:
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
And, I am doing it as below
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is giving an error:
error: "SequelizeValidationError: string violation: Id cannot be an array or an object"
It is looks like it is not calling the second api for every Id
You may want to take a look a forkJoin
import { Observable, forkJoin } from 'rxjs';
And then
ngOnInit() {
let one = this.http.get('some/api/1') //some observable;
let two = this.http.get('some/api/2') // another observable;
forkJoin([one, tow]).subscribe(response => {
// results[0] is our one call
// results[1] is our second call
let var1 = response[1];
let var2 = response[0];
}/*, error => { in case error handler } */);
}
Wouldn't it be better to use Promise.all() once more?
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
And using toPromise() will make the code more concise.
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
return this.serviceB.retry(oretry).toPromise();
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).toPromise();
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
Use combineLatest, in Angular we use RxJs not promises.
combineLatest(
[this.http.get('call1'), this.http.get('call2')]
).subscribe(([result1, result2]) => {
// do stuff with result1 and result2
});
promise.all takes input in an array and gives response in an array,
Create 2 functions each with your asynchronous logic returning a promise,
Say funcA and funcB, then use below to invoke them parellely
Promise.all([funcA(this.hostName), funcB(this.id)])
.then(respones => {
console.log(responses[0]); //return value for funcA
console.log(responses[1]); //return value for funcB
})
.catch(err => console.log(err));
I am assuming your logic of functions are correct, I just copy-pasted from your question and gave them structure
const funcA = (hostName) => {
hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
});
}
const funcB = (Id) => {
Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
}

Multiple if's refactoring

I have this function, with two ifs where I want to find the user depending on which alphanumeric code I receive. How can I refactor this one with sanctuary-js?
//const code = '0011223344';
const code = 'aabbc';
const isNumberCode = code => !!/^[0-9]{10}$/.exec(code);
const isLiteralCode = code => !!/^[A-Za-z]{5}$/.exec(code);
const findUser = (criteria) => {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('user object');
}, 300);
});
}
async function handler(code) {
if (isNumberCode(code)) {
const user = await findUser({id: code});
return user;
}
if (isLiteralCode(code)) {
const user = await findUser({identifier: code});
return user;
}
return 'not found';
}
async function run() {
const user = await handler(code);
console.log(user)
}
run();
I can't understand how I should handle three different types: number code, literal code and not found code.
-- UPDATE
Here my functional solution (I may think so):
const Code = x => ({
chain: f => f(x),
fold: (e, a, f) => e(x)
});
const ErrorCode = x => ({
fold: (e, a, f) => e(x),
findLabel: f => ErrorCode(x)
});
const PromiseToCode = promise => ({
fold: (e, a, f) => promise.then(x => x.fold(e, a, f))
});
const NumberCode = x => ({
fold: (e, a, f) => a(x),
findLabel: f => PromiseToCode(f(x, {activationCode: x}, NumberCode))
});
const LiteralCode = x => ({
fold: (e, a, f) => f(x),
findLabel: f => PromiseToCode(f(x, {finderCode: x}, LiteralCode))
});
const checkTypeOfCode = code => {
if (isNumberCode(code)) {
return NumberCode(code);
}
if (isLiteralCode(code)) {
return LiteralCode(code);
}
return ErrorCode(code);
};
const find = async (code, criteria, type) => {
const user = findUser();
if (!user) {
return ErrorCode(code);
}
return type(user);
};
const handler2 = (code) =>
Code(code)
.chain(checkTypeOfCode)
.findLabel(find)
.fold(
e => 'not found',
a => 'user object find by id',
l => 'user object find by identifier'
)
handler2(code).then(console.log);
But I don't know if it's good code. Also I'm asking about sanctuary-js because I think that all this object not good way to programming.
Since you are looking for a more functional restructuring, you can try this:
Divide your code into smaller, more independent sections:
findUser: This function is responsible to give either UserObject or Not found.
Create a function getCriteria, that will have all the logic as to isNumberCode or isLiteralCode etc. This will return a criteria object or undefined.
handler should be responsible to get criteria, and based on that return findUser's response. Any cleanup code can be kept here but this is a hub function which calls various functions and return an output. It should have bare minimum business logic.
//const code = '0011223344';
const code = 'aabbc';
const isNumberCode = code => !!/^[0-9]{10}$/.exec(code);
const isLiteralCode = code => !!/^[A-Za-z]{5}$/.exec(code);
const findUser = (criteria) => {
return new Promise(function(resolve, reject) {
if (!criteria) resolve('not found')
setTimeout(function() {
resolve('user object');
}, 300);
});
}
function getCriteria(code) {
if (isNumberCode(code)) {
return { id: code };
}
if (isLiteralCode(code)) {
return { identifier: code }
}
}
async function handler(code) {
const user = await findUser(getCriteria(code))
return user;
}
async function run() {
const user = await handler(code);
console.log(user)
}
run();
You can create enum for multiple type of input and use switch statement as below.
// Enum for search-parameters
var ParameterTypes =
{
NUMBER :1 ,
LITERAL:2 ,
OTHER : 3
}
function getParameterType()
{
//responsible to get search-parameter
return isNumberCode(code) ? ParameterTypes.NUMBER :
( isLiteralCode(code) ? ParameterTypes.LITERAL : ParameterTypes.OTHER);
}
async function handler(code)
{
//responsible to search user
var user;
switch(getParameterType())
{
case ParameterTypes.NUMBER :
user = await findUser({id: code});
//console.log('number');
break;
case ParameterTypes.LITERAL :
user = await findUser({identifier: code});
//console.log('literal');
break;
case ParameterTypes.OTHER :
user = 'not found';
//console.log('other');
break;
}
return user;
}

How to test class with chain promises using jest?

I have use case where i have to test promise chaining so in below code i tried to mock all the function that are being called in actual code and using jest.fn() but getting some error TypeError: Cannot read property 'then' of undefined
any what is implemented wrong in below code or any better approach to write async promises chaining ?
main.ts
export class ModuleExecutor {
public execute(moduleName: string): (param1, param2) => any {
const self = this;
return function (params: any, responseCallback: (param: any, param2: any) => any) {
let _mod;
let _httpRequest;
let _params;
Promise.resolve(getApiModule(self.identity, moduleName))
.then((mod: ModuleBase<any, any>) => {
_mod = mod;
mod.ExecStage = ExecStage.Init;
// #ts-ignore - red squiggly for the return mismatch which are resolved in Grunt
return mod.init(getHttpModule(self.identity), params);
})
.then((httpRequest: HttpRequestBase) => {
_httpRequest = httpRequest;
if (_mod.Response().Summary.Header) {
throw _mod.Response().Summary;
}
return httpRequest;
})
.then((params1: any) => {
_params = params2;
_mod.ExecStage = ExecStage.Core;
return _mod.core(_params, _httpRequest);
})
.catch((e) => {
return e;
});
};
}
}
main.spec.ts
import {ModuleExecuter} . from './main.ts';
const _executer = new ModuleExecutor(Identity.node);
const myMockFunc = jest.fn(() => {
// _executer.execute("Payments/accountBalance/GetAccount001");
executer = new ModuleExecutor(Identity.node);
executerSpy = executer.execute("Payments/accountBalance/GetAccount001");
const _promise1 = new Promise(function(resolve) {
// moduleExecutor.execute(params, callback function)
executerSpy(params, function(data) {
resolve(data);
}).catch((e) => {
expect(e).toBeTruthy();
});
});
Promise.resolve(_promise1);
});
const myMockFuncExecute = jest.fn(() => {
Promise.resolve(getApiModule(Identity.node, "Payments/accountBalance/GetAccount"));
});
const myMockModuleBase = jest.fn((mod: ModuleBase<any, any>) => {
// _executer.execute("Payments/accountBalance/GetAccount001");
mod.ExecStage = ExecStage.Init;
// #ts-ignore - red squiggly for the return mismatch which are resolved in Grunt
Promise.resolve(mod.init(getHttpModule(Identity.node), params));
});
const myMockHttpRequest = jest.fn((httpRequest: HttpRequestBase) => {
Promise.resolve(httpRequest);
});
it('should your test scenario', (done) => {
myMockFunc()
.then((data) => {
expect(myMockFuncExecute).toBeCalledWith("Payments/accountBalance/GetAccount001");
expect(myMockModuleBase).toBeCalledWith(ModuleBase);
expect(myMockHttpRequest).toBeCalledWith(HttpRequestBase);
expect(data).toEqual({test: "test"});
done();
});
});

TypeScript/RxJS - Observable subscribe() method complete() not running

I've had a good look around to try and solve this but can't find an answer that works.
I'm trying to implement a callback for an additional function when a subscribe() method successfully returns an 'contacts$' observable, but using complete() on the subscription does not do anything.
I've also tried using finally() on the observable as suggested elsewhere, but this also doesn't work.
Using complete():
ngOnInit() {
this.getContacts().subscribe(
data => {
this.contacts = data;
console.log('NewInvoice.contacts:', data);
this.selectedContactId = this.contacts[0].id;
console.log('selectedContactId: ' + this.selectedContactId);
},
error => {
console.error('Error getting contacts via subscribe() method:', error);
},
() => {
this.getSelectedContact();
}
)
}
Using finally():
ngOnInit() {
this.getContacts()
.finally(() => console.log('a'))
.subscribe(
data => {
this.contacts = data;
console.log('NewInvoice.contacts:', data);
this.selectedContactId = this.contacts[0].id;
console.log('selectedContactId: ' + this.selectedContactId);
},
error => {
console.error('Error getting contacts via subscribe() method:', error);
},
() => {
this.getSelectedContact();
}
)
}
Method for callback on observable completion:
getSelectedContact() {
this.contactsCollection.doc(this.selectedContactId).ref.get().then(snapshot => {
this.selectedContact = snapshot.data() as Contact;
console.log('selectedContact:', this.selectedContact);
})
}
Difficult to say without more info, but I'll give a shot:
ngOnInit() {
this.getContacts()
.subscribe(
data => {
this.contacts = data;
console.log('NewInvoice.contacts:', data);
this.selectedContactId = this.contacts[0].id;
console.log('selectedContactId: ' + this.selectedContactId);
},
error => {
console.error('Error getting contacts via subscribe() method:', error);
},
() => {
this.getSelectedContact()
.asObservable()
.subscribe((a) => console.log(a));
}
)
}
And :
getSelectedContact() {
return this.contactsCollection.doc(this.selectedContactId).ref.get().then(snapshot => {
this.selectedContact = snapshot.data() as Contact;
console.log('selectedContact:', this.selectedContact);
})
}
Or a little cleaner :
const callback = (a) => console.log(a);
...
() => {
this.getSelectedContact(callback);
}
...
getSelectedContact(callback) {
this.contactsCollection.doc(this.selectedContactId).ref.get()
.then(snapshot => {
this.selectedContact = snapshot.data() as Contact;
console.log('selectedContact:', this.selectedContact);
})
.then(a => callback(a));
}
Lastly as #Picci suggested :
this.getContacts()
.last()
.exhaustMap((data) => this.getSelectedContact(data))
.map(a => console.log(a))
.subscribe();
Beware that all the above code is absoultely not tested and only for reference.

how to EventSource with Redux Observable

The question is simple, how can i use redux-observable with an EventSource?
With RxJs its like:
const observable = Observable.create(observer => {
const eventSource = new EventSource('/model-observable');
return () => {
eventSource.close();
};
});
observable.subscribe({
next: data => {
this.zone.run(() => this.someStrings.push(data));
},
error: err => console.error('something wrong occurred: ' + err)
});
This sounds more like a general RxJS question about how to connect to an EventSource. This can be done in a number of ways. If all you care about are the messages (and not errors/open):
import { fromEvent } from 'rxjs/observable/fromEvent';
const fromEventSource = url => {
return new Observable(observer => {
const source = new EventSource(url);
const message$ = fromEvent(source, 'message');
const subscription = message$.subscribe(observer);
return () => {
subscription.unsubscribe();
source.close();
};
});
};
If you care about open and/or errors, it requires a little more code to pipe everything together:
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
const fromEventSource = (url, openObserver) => {
return new Observable(observer => {
const open = new Subscriber(openObserver);
const source = new EventSource(url);
const onOpen = event => {
open.next(event);
open.complete();
};
const onError = event => {
if (event.readyState === EventSource.CLOSED) {
observer.complete();
} else {
observer.error(event);
}
};
const onMessage = event => {
observer.next(event.data);
};
source.addEventListener('open', onOpen, false);
source.addEventListener('error', onError, false);
source.addEventListener('message', onMessage, false);
return () => {
source.removeEventListener('open', onOpen, false);
source.removeEventListener('error', onError, false);
source.removeEventListener('message', onMessage, false);
source.close();
};
});
};
fromEventSource('http://some-url.com')
.subscribe(value => console.log(value));
Usage in redux-observable would be something like this:
const somethingEpic = action$ =>
action$.ofType(SOMETHING)
.mergeMap(() =>
fromEventSource('http://some-url.com')
.map(message => ({
type: MESSAGE,
payload: message
}))
.catch(e => Observable.of({
type: SOMETHING_ERROR,
payload: e,
error: true
}))
);

Categories