Rxjs http chained request error handling - reactjs

In the following code, consider each switchMap as step.
Step 1 : Create user if not already available
Step 2 : Create conversation
Step 3 : Return Response or Error
We might get business side exception in step 1 or step 2 and would like to handle it elegantly. Do we have better way of handling this ? e.g. Just skip step 2 if we get error in step 1. We tried a lot but not able to get better solution. If we simply throw error in step 1
Observable.throw(error)
automatic unsubscribtion is happening.
const createNewConversationEpic: Epic<Action<{}>, RootState> = (
action$: ActionsObservable<Action<Conversation | User | Error>>
) => {
return action$
.ofType(ConversationsActions.CREATE_NEW_CONVERSATION).pipe(
switchMap((action: Action<User>) => {
return action.payload.id
? Observable.of(action.payload)
: createNewLead(action.payload).pipe(
map(data => data),
catchError(error => {
return Observable.of(error);
})
);
}),
switchMap((response) => {
if (!(response instanceof Error)) {
return createNewConversation({ userId: response.id.toString() }).pipe(
map(data => ConversationsActions.CreateNewConversationSuccess(data)),
catchError(error => {
return Observable.of(error);
})
);
} else {
return Observable.of(response);
}
}),
switchMap(response => {
if (response instanceof Error) {
return ActionsObservable.of(
ConversationsActions.CreateNewConversationError(response),
ConversationsActions.MessagingGlobalError(response),
ConversationsActions.ResetMessagingGlobalError()
);
} else {
return Observable.of(response);
}
})
);
};
export const createNewLead = (body: {}) => {
return request('/api/v1/lead/create/mobile', AjaxMethod.POST, body);
};
const request = (path: string, method: AjaxMethod, body: {}) => {
const url = path;
return ajax({
body,
headers: {
Authorization: 'Bearer ' + getAuthToken(),
'Content-Type': 'application/json'
},
method,
responseType: 'json',
timeout: 120000, // 2 min
url
})
.map(e => {
console.log('[AJAX] Status --- ' + e.status);
console.log(e.response);
return e.response;
})
.catch(err => {
console.log(err);
let error = 'Error while executing request';
if (err.status === 400 || err.status === 412) {
if (err.response.error) {
error = err.response.error;
} else {
error = err.response.message;
}
}
// Handle 401 Status
if (err.status === 401) {
clearLocalstorage();
window.location.href =
window.location.origin +
'/authentication/?src=' +
window.location.pathname;
}
if (err.status === 403) {
error = 'Oops! Looks like you don\'t have access to it';
}
return Observable.throw(new Error(error));
});
};

If you need to halt the automatic unsubscribe, you just need to wrap the pipeline that you expect to have an error in another stream that can handle the exception, just like you would a standard try/catch, as long as you capture the error and handle it before returning to the outer stream the parent subscription remains intact.
const createNewConversationEpic: Epic<Action<{}>, RootState> = (
action$: ActionsObservable<Action<Conversation | User | Error>>
) => {
return action$
.ofType(ConversationsActions.CREATE_NEW_CONVERSATION).pipe(
// Parent Stream
switchMap((action: Action<User>) =>
// Start child stream
iif(() => action.payload.id,
Observable.of(action.payload),
createNewLead(action.payload)
).pipe(
// Process event as a "happy-path" since errors get forwarded to the end
switchMap((response) => createNewConversation({ userId: response.id.toString() })),
// Move this inline with the rest of the inner pipe line.
map(data => ConversationsActions.CreateNewConversationSuccess(data)),
// Catch all errors from this inner pipeline this will stop them from
// propagating to the outer stream.
catchError(e => ActionsObservable.of(
ConversationsActions.CreateNewConversationError(e),
ConversationsActions.MessagingGlobalError(e),
ConversationsActions.ResetMessagingGlobalError()
)
)
)
};

Related

How to make url api option short?

In this case I created a recursive promise function that is used to call different api urls. How do make the switch-case option in this code not too long?
let listUrl: Array<string> = ['URL-1', 'URL-2', 'URL-3', 'URL-n']
const rpcCall = async (baseURL: string) => {
switch (baseURL) {
case 'URL-1':
return new Promise((_resolve, reject) => fetch(baseURL)
.then(resolve => resolve.status !== 200 || 201 ? Promise.reject() : resolve)
.catch(reject)
);
case 'URL-2':
return new Promise((_resolve, reject) => fetch(baseURL)
.then(resolve => resolve.status !== 200 || 201 ? Promise.reject() : resolve)
.catch(reject)
);
case 'URL-3':
return new Promise((_resolve, reject) => fetch(baseURL)
.then(resolve => resolve.status !== 200 || 201 ? Promise.reject() : resolve)
.catch(reject)
);
case 'URL-n':
return new Promise((_resolve, reject) => fetch(baseURL)
.then(resolve => resolve.status !== 200 || 201 ? Promise.reject() : resolve)
.catch(reject)
);
default:
return Promise.reject();
}
}
const checkToken = async (index = 0): Promise<string> => {
new Promise((res, rej) =>
rpcCall(listUrl[index])
.then(res)
.catch((reject) => {
if (index < baseUrl.length && reject) {
return checkToken(index + 1)
.then(res)
.catch(rej);
} else {
rej();
}
})
);
return listUrl[index]
}
I mean the best practice method for the options
Your URLs cases are all the same, so they can be combined. You should also avoid the explicit Promise construction antipattern. 200 || 201 will not properly compare against both values either. The resolver function is also not the same as the Response object from fetch - better to name the parameter appropriately.
You don't need to construct another Promise in checkToken either - and that function also looks broken because it's creating a Promise that isn't being used anywhere. Another problem is that a Response object isn't a string; : Promise<string> is not an accurate typing. Perhaps you meant to use response.text() and something like
let listUrl: Array<string> = ['URL-1', 'URL-2', 'URL-3', 'URL-n']
const rpcCall = (baseURL: string) => {
const urls = ['URL-1', 'URL-2', 'URL-3', 'URL-n'];
if (!urls.includes(baseURL)) {
return Promise.reject();
}
return fetch(baseURL)
.then(response => {
if (response.status !== 200 && response.status !== 201) {
throw new Error();
}
return response.text();
});
};
const checkToken = async (index = 0): Promise<string> => {
return rpcCall(listUrl[index])
.catch((error) => {
if (index < listUrl.length && error) {
return checkToken(index + 1)
} else {
throw new Error(error);
}
});
};
Since this is TypeScript, you could also consider changing rpcCall to require that the string passed is one of the permitted URLs, thus forcing the caller to validate the potential argument first.
const urls = ['URL-1', 'URL-2', 'URL-3', 'URL-n'];
const rpcCall = (baseURL: typeof urls[number]) => {
return fetch(baseURL)
// etc

convert nested AngularJS service to Angular Observable service

I have some AngularJS(pre 1.5) services using nested calls in a project that we are rebuilding in Angular(11).
The services use nested calls but I have no idea how to rebuild them using RXJS.
Any help, or detailed links to understand how I can get the result I need would be great.
I have not been able to find anything sofar that helps me understand how to resolve this.
This is the original service:
function getBalanceGroups() {
return $http.get(url.format("/accounts/{{accountId}}/balance-groups", $stateParams))
.then(function (response) {
_.each(response.data, function (item) {
getBalanceViews(item.accountBalanceGroupId)
.then(function (balanceViewData) {
item.balanceViews = _.sortBy(balanceViewData, function (view) {
return (view.balanceViewId === item.totalIndebtednessViewId) ? 0 : 1;
});
_.each(item.balanceViews, function (view) {
getBalanceSegments(view.accountBalanceViewId)
.then(function (balanceSegmentData) {
view.balanceSegments = balanceSegmentData;
view.totalBalance = 0;
view.totalBalance = _.sumBy(view.balanceSegments, "balance");
});
});
});
});
response.data = _.sortBy(response.data, function (item) {
return (item.isActive && item.isPrimary) ? 0 : 1;
});
return new LinkedList(response.data);
}, function (error) {
$log.error('Unable to return balance group for the balanceChiclet');
});
}
This is what I have so far: (not working - it is returning the final api data response, I need to use the data to use the data to modify the previous response and return the modified data. No idea how )
getBalanceGroups(accountId: number | string): Observable<any> {
let balGroupsUrl = `/accounts/${accountId}/balance-groups`;
return this.http.get(`${this.baseUrl}${balGroupsUrl}`).pipe(
mergeMap( (groups: any) => groups),
flatMap((group:any) => {
group.balanceViews = [];
return this.getBalanceViews( group.accountBalanceGroupId, group )
}),
mergeMap( (views: any) => views),
flatMap((views: any) => {
return this.getBalanceSegments( views.accountBalanceViewId )
}),
catchError((err) => of(err) ),
tap( groups => console.log('groups: 3:', groups) ),
)
}
private getBalanceViews(accountBalanceGroupId: number | string, group): Observable<any> {
let balViewsUrl = `/balance-groups/${accountBalanceGroupId}/balance-views`;
return this.http.get(`${this.baseUrl}${balViewsUrl}`);
}
private getBalanceSegments(accountBalanceViewId: number | string): Observable<any> {
let balSegUrl = `/balance-views/${accountBalanceViewId}/balance-segments`;
return this.http.get(`${this.baseUrl}${balSegUrl}`);
}
Instead of the mergeMap + flatMap (they are synonymous BTW), you could use forkJoin to trigger multiple requests in parallel.
You might have to use multiple nested forkJoin given the nature of the request.
While I've converted the loadash sumBy using Array#reduce, I've left the sort incomplete for you to do it.
Try the following
getBalanceGroups(): Observable<any> {
return this.http.get(`/accounts/${accountId}/balance-groups`, { params: stateParams }).pipe(
switchMap((response: any) =>
forkJoin(
response.data.map((item: any) =>
getBalanceViews(item.accountBalanceGroupId, item).pipe(
map((balanceViewData: any) => ({
...item,
balanceViews: balanceViewData.sort() // <-- incomplete
})),
switchMap((item: any) =>
forkJoin(
item.balanceViews.map((view: any) =>
getBalanceSegments(view.accountBalanceGroupId).pipe(
map((balanceSegmentData: any) => ({
...item,
balanceSegments: balanceSegmentData,
totalBalance: view.balanceSegments.reduce((acc, curr) => acc += curr['balance'], 0)
}))
)
)
)
)
)
)
)
),
map((response: any) => ({
...response,
response.data: response.data.sort() // <-- incomplete
})),
catchError((error: any) => {
console.error('Unable to return balance group for the balanceChiclet');
return of(error);
})
);
}

Ignoring failed promises in Promoise.all

I am fetching URLs specified in the array and then combine fetched results.
I want to ignore failed fetches.
While are tons of post on the subject:
Wait until all promises complete even if some rejected
https://gist.github.com/nhagen/a1d36b39977822c224b8
I just can't figure our how to apply this to my code, that fetches URLs from the array:
Promise.all (arrayOfBlobs.map (x => fetch (x).then (response => response.json())) )
.then (json => {
json.forEach ( x => {
if (Array.isArray (x)) {
// this json has array of objects
console.log (`Received ${x.length} prospects`)
x.forEach ( y => combinedArray.push (y) )
}
else {
// this json has single prospect object
console.log (`Received single prospect`)
combinedArray.push (x)
}
})
this.setState({loadingTable: false, data: combinedArray})
})
.catch (error => {
console.error (error.message)
this.setState({loadingTable: false, data: combinedArray})
})
For example below did not work:
Promise.all (arrayOfBlobs.map (x => fetch (x).then (response => response.json())) )
.then (json => {
json.forEach ( x => {
if (Array.isArray (x)) {
// this json has array of objects
console.log (`Received ${x.length} prospects`)
x.forEach ( y => combinedArray.push (y) )
}
else {
// this json has single prospect object
console.log (`Received single prospect`)
combinedArray.push (x)
}
})
.catch (e => {console.log (`Failed to fetch due to ${e.message}`)})
this.setState({loadingTable: false, data: combinedArray})
})
.catch (error => {
console.error (error.message)
this.setState({loadingTable: false, data: combinedArray})
})
What do I need to do to modified my code so failed fetches are ignored?
Use Promise.allSettled() it will return that status of fulfilled/rejected with the value.
More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
The catch is supposed to go on the individual fetch promise inside the map:
Promise.all(arrayOfBlobs.map(x =>
fetch(x)
.then(response => response.json())
.catch(e => {
// ^^^^^^^
console.log(`Failed to fetch due to ${e.message}`);
return null; // this value will get into the `json` array
})
))
.then(json => {
const combinedArray = [];
for (const x in json) {
if (Array.isArray(x)) {
// this json has array of objects
console.log (`Received ${x.length} prospects`)
combinedArray.push(...x);
} else if (x != null) {
// this json has single prospect object
console.log (`Received single prospect`)
combinedArray.push(x)
} else { // x == null
console.log(`(ignored error)`)
})
}
this.setState({loadingTable: false, data: combinedArray})
})
Also don't forget to handle http (status code) errors!
A version of Promise.allSettled that removes rejected results:
function allFullfilled(promises) {
const responses = [];
let settledCount = 0;
return new Promise(res => {
for(const promise of promises) {
promise
.then( promiseResult => {
responses.push(promiseResult);
})
.catch(err => {
// ignore
})
.then( () => {
settledCount++;
if (settledCount === promises.length) {
res(responses);
}
});
}
});
}

Promise Ionic/Angular - user connection

I'm trying to use promise in order to connect user on my Ionic App.
I tried many things... but that doesn't work. What's the problem ? Can you help me please ?
I have to execute my load function before verify "access".
This is my code currently. What's wrong ? (Currently, the error is : "A function whose declared type is neither 'void' nor 'any' must return a value.")
This is my load function (no problem whith it, it's what I must execute in first)
load(credentials)
{
let headers : any = new HttpHeaders({ 'Content-Type': 'application/json' }),
options : any = { "pseudo" : credentials.email, "mdp" : credentials.password },
url : any = "http://localhost:8888/authVerif2.php" ;
return new Promise((resolve) => {
this.http
.post(url, JSON.stringify(options), headers)
.subscribe(data => {
console.dir(data);
this.infosUser = data;
//console.log(this.infosUser[0].prenom);
resolve(this.infosUser);
},
(error : any) =>
{
console.dir(error);
});
})
}
This is my login function :
public login(credentials) : Observable<any>{
this.load(credentials)
.then( a =>
{
if (credentials.email === null || credentials.password === null) {
return Observable.throw("Please insert credentials");
} else {
return Observable.create(observer => {
// At this point make a request to your backend to make a real check!
let access = (credentials.password === "pass" && credentials.email === "email");
this.currentUser = new User('Simon', 'saimon#devdactic.com');
observer.next(access);
observer.complete();
});
}
}
);
}
these two functions are in a provider named "auth-service".
This provider is use in my page "login.html"
public login() {
this.showLoading()
this.auth.login(this.registerCredentials).subscribe(allowed => {
if (allowed) {
this.nav.setRoot(HomePage);
} else {
this.showError("Access Denied");
}
},
error => {
this.showError(error);
});
}
Do not stringify options beacause you have to post data to your login function in object format, if you stringify the options ,it means you are posting a string. so, you can't access data in login function from credentials.email & credentials.password

Returning NodeJS console value into client side (AngularJS)

I''m totally new to AngularJS and NodeJs. I'm having a promise and it works well. There's console.log here ,this works well in server console.I need to print this console.log into client side (It's in Angular JS).
This is my server side code.
function checkNamesAvailable(
name /* : string | void */,
) /* :Promise<Object[]> */ {
const connection = createConnection()
return new Promise((resolve, reject) => {
const sql =
`SELECT names
FROM Names
WHERE JSON_EXTRACT(names, '$.name') = ? `
const values = [name]
const query = connection.query(sql, values, (error, results, fields) => {
connection.end()
if (error) {
return reject(error)
console.log(error)
}
resolve(results)
})
})
.then((results) => {
if(results.length > 0){
console.log("name is already exist")
}else{
saveNewName(names)
}
})
}
I'm calling above function in index.js as follows
addresses.post = function (
request /* : Object */,
response /* : Object */
) /* : Promise<Object> */ {
return authentication.authenticate((request.headers || {}).authorization)
.then((authorised) => {
if (!authorised) {
return Promise.reject(boom.forbidden('You do not have access to add new names'))
}
libAddr.checkNamesAvailable(request.body.data.attributes.names)
.then(() => {
return response.setStatusCode(200).setPayload({
})
})
.catch(err => {
return response.setStatusCode(400).setPayload({
message: err
})
})
Could someone help me regarding this?
You need to send your error message to client.
As like:
res.status(500).send({ error: 'Something failed!' });
And inside your AngularJS controller, you need to catch this error. For example:
$http.get('/url')
.then(function(result){
console.log('good!');
})
.error(function(error){
$scope.myErrorMessage = error.error;
});
And now you can show it on the page
<div ng-if="myErrorMessage">{{ myErrorMessage }}</div>
Please check the below code snippet:
addresses.post = function (
request /* : Object */ ,
response /* : Object */
) /* : Promise<Object> */ {
return authentication.authenticate((request.headers || {}).authorization)
.then((authorised) => {
if (!authorised) {
return Promise.reject(boom.forbidden('You do not have access to add new names'))
}
libAddr.checkNamesAvailable(request.body.data.attributes.names)
.then((results) => {
return response.setStatusCode(200).setPayload({
data: results
});
})
.catch(err => {
return response.setStatusCode(400).setPayload({
message: err
});
})

Resources