How to make url api option short? - reactjs

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

Related

Change table data set based on Google Maps API results

I am trying to implement a filter which allows me to filter users based on the distance between a location and their address. Data is provided to the table using useMemo, basically like this:
const data = useMemo(
() =>
contacts.filter(contact => {
var shouldReturn = true;
clientFilter.map((filter, i) => {
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
calculateDistance(originAddress, filter.value, function(distance) {
console.log('distance is calculated: ', distance);
if (distance > 10000) {
console.log('distance is > for', contact['name']);
shouldReturn = false;
}
});
}
});
return shouldReturn;
}),
[clientFilter]
);
This works fine, in console the results return as I expect them to be. However, my table doesn't update. I suspect it is because the result of the API calls are async, and thus the table is re-rendered before the results are in.
I have tried updating the data using useEffect, but this brings me in a loop which constant re-renders, and thus exceeding the maximum (Maximum update depth exceeded.).
How should I go about this? Should I try async functions? If so, how can I wait to update my data until all promises are resolved?
EDIT 14 NOV
So, I have been looking further into this today. I have managed to switch the filtering to useEffect() instead of useMemo(), so currently, it looks like this:
const [filteredContacts, setFilteredContacts] = useState(contacts);
const data = useMemo(() => filteredContacts, [filteredContacts]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
console.log('Log: In useEffect()');
if (!isLoading) {
setIsLoading(true);
(async () => {
console.log('Log: State has changed, filtering will start');
filterContacts().then(() => setIsLoading(false));
})();
}
}, [clientFilter]);
async function filterContacts() {
setFilteredContacts(
contacts.filter(contact => {
var shouldReturn = true;
clientFilter.map((filter, i) => {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value &&
shouldReturn
) {
shouldReturn = false;
}
if (filter.condition === 'max_10km' && shouldReturn) {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
calculateDistance(originAddress, filter.value, async function(
distance
) {
console.log('Log: Distance is calculated: ', distance);
if (distance > 10000) {
console.log(
'Log: Distance is further away for',
contact['title']
);
shouldReturn = false;
}
});
}
});
console.log('Log: About to return shouldReturn value');
return shouldReturn;
})
);
}
Now, this works for my other filters, but the async distance calculation still runs after the return of shouldReturn has been done. So my logs look like this (I have 16 contacts/users currently):
Log: In useEffect()
Log: State has changed, filtering will start
(16) Log: About to return shouldReturn value
Log: Distance is calculated: 1324
Log: Distance is calculated: 4326
...
So basically, it still ignores the async state of my function calculateDistance. Any ideas?
EDIT 15/11
Might be useful as well, this is my calculateDistance() function:
function calculateDistance(origin, destination, callback) {
const google = window.google;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: google.maps.TravelMode.DRIVING
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
callback(result.routes[0].legs[0].distance.value);
} else {
console.error('Google Maps API error: ', status);
callback(null);
}
}
);
}
EDIT 18/11
After #Secret's suggestion, I changed the code to:
const shouldRemoveContact = await(filters, contact) = () => {
for (const filter of filters) {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value
) {
return true
}
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`
// please update calculateDistance to return a promise
const distance = await calculateDistance(originAddress, filter.value)
return distance > 10000
}
return false
}
}
async function filterContacts (filters, contact) {
// for every contact, run them to shouldRemoveContact
// since shouldRemoveContact is async, we use Promise.all
// to wait for the array of removeables to be ready
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// use shouldRemove to check if contact should be removed
// and voila!
return contacts.filter((c, i) => !shouldRemove[i])
}
This results in:
Syntax error: Unexpected reserved word 'await'.
on the line:
const shouldRemoveContact = await(filters, contact) = () => {
You are correct in your mistake - your calculateDistance is async and therefore you can't filter your data properly. Essentially, your code can be boiled down to:
setFilteredContacts(
contacts.filter(contact => {
var shouldReturn = true;
// THIS IS WHERE CALCULATE IS
// but it doesn't do anything because it is async
// so effectively, it does nothing like a comment
console.log('Log: About to return shouldReturn value');
return shouldReturn;
})
);
As you can see, your contacts will never be filtered out as shouldReturn will always return true - it never changes in the middle of that function!
What you can do is calculate the list of filterables beforehand, and then use that to run your filter. Something like this (psuedocode):
// given a contact and a list of filters
// asynchronously return if it should or should not be filtered
const shouldRemoveContact = async () => {}
// THEN, in the filterContacts part:
// let's generate an array of calls to shouldRemoveContact
// i.e. [true, true, false, true], where `true` means it should be removed:
// note that we use await Promise.all here, waiting for all the data to be finished.
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// we then simply shouldRemove to filter it all out
return contacts.filter((c, i) => !shouldRemove[i])
All together:
// given a list of filters and one contact
// asynchronously returns true or false depending on wheter or not
// the contact should be removed
const shouldRemoveContact = async (filters, contact) => {
for (const filter of filters) {
if (
filter.condition === 'equal' &&
contact[filter.field] != filter.value
) {
return true
}
if (filter.condition === 'max_10km') {
const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`
// please update calculateDistance to return a promise
const distance = await calculateDistance(originAddress, filter.value)
return distance > 10000
, async function(
distance
) {
console.log('Log: Distance is calculated: ', distance);
if (distance > 10000) {
console.log(
'Log: Distance is further away for',
contact['title']
);
shouldReturn = false;
}
});
}
}
return false
}
async function filterContacts (filters, contact) {
// for every contact, run them to shouldRemoveContact
// since shouldRemoveContact is async, we use Promise.all
// to wait for the array of removeables to be ready
const shouldRemove = await Promise.all(
contact.map(c => shouldRemoveContact(filters, c))
)
// use shouldRemove to check if contact should be removed
// and voila!
return contacts.filter((c, i) => !shouldRemove[i])
}

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);
})
);
}

(Refactor/Improve) Loop to make API calls and manupilate Array following the "no-loop-func"

Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};

React Context API; Is it possible to access updated context from a sibling hook

A button calls the function signAllBrowsed, which contains two other functions:
loadSafetyLetters is a hook that makes a database call for some data and sets it in context
signAll is a hook that tries to access data in context to do something with it
Context is getting set properly, but when signAll accesses it, the data is not updated. Is there a way to access the updated context without directly passing it to the 2nd function? Or is there a way to call a callback once context is updated and accessible? Seems the updated context is only available after a re-render.
The component containing signAllBrowsed and the 2 hooks are siblings.
code in above image:
setModalVisible(true)
const logHeader = 'SafetyLetterHome::SignAllBrowsed'
try {
const response = await loadSafetyLetters(false) // before beginning sign all, get a fresh list of letters from db
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
if (response === 'no api error') {
await signAll()
navigation.navigate('SafetyLetterSign')
}
} catch (error) {
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert('Database Error', errorMessage)
console.log(`${logHeader}`, errorMessage)
}
}
loadSafetyLetters calls the loadLetters hook:
const [getLetters] = useGetLetters()
const [sortLetters] = useSortLetters()
const [hasAPIError] = useHasAPIError()
const navigation = useNavigation()
const { setModalVisible, setShowSignAll, setSortedLetters, setUnsortedLetters } = useContext(SafetyContext)
const loadLetters = async (sort = true) => {
try {
const response = await getLetters()
const logHeader = 'SafetyHome::loadLetters'
const errorMessage = 'The following error occurred when trying to load letters:'
if (Configs.SHOW_REQUEST_LOGS) console.log(`${logHeader} response`, response)
const error = hasAPIError(response, logHeader, errorMessage)
if (error) return error
const { data } = response.data.payload
let unsortedLetters = []
if (data !== null && data.length > 0) {
data.map((item) => {
// grab only unsigned letters
if (
item.assignmentStatus === SafetySources.PENDING ||
item.assignmentStatus === SafetySources.BROWSED ||
item.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
unsortedLetters.push({
safetyLetterId: item.safetyLetterId,
title: item.title,
assignmentStatus: item.assignmentStatus,
filePath: item.filePath,
embeddableToken: item.embeddableToken,
sponsorId: item.sponsorId,
letterDate: item.letterDate,
form16: item.form16Enabled === '1' ? true : false,
sponsorName: item.sponsorName,
type: item.letterType,
sortOrder: item.sortOrder, // dear doctor; sortOrder === 1
})
}
})
}
if (unsortedLetters.length > 0) {
let bletters = unsortedLetters.filter((letter) => letter.assignmentStatus === SafetySources.BROWSED || letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE)
console.log('useLoadLetters; setting fresh pull of letters in context, including ', bletters.length, ' browsed letters')
setUnsortedLetters(unsortedLetters) // set in context
setShowSignAll( // show/hide sign all button
unsortedLetters.some((letter) =>
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE,
))
}
if (sort) {
if (unsortedLetters.length > 0) {
let sortedLetters = sortLetters(unsortedLetters) // sort letters with hook
setSortedLetters(sortedLetters) // set in context
}
}
} catch (error) {
console.log('SafetyHome::loadLetters ', error)
const errorMessage = error.status && error.status.message ? error.status.message : error
Alert.alert(
'Error Loading Letters',
`A database error has occurred. Please try again. (${errorMessage})`,
)
navigation.navigate('Home')
} finally {
setModalVisible(false)
}
}
return [loadLetters]
}
signAll hook:
const { state: { unsortedLetters },
setF16Browsed,
setQcAndBrowsed,
setModalVisible,
setSelectedLetter
} = useContext(SafetyContext)
const signAll = async () => {
let qcAndBrowsed = [] // set letter groups in context
let f16Browsed = []
unsortedLetters.forEach((letter) => {
if (
letter.assignmentStatus === SafetySources.BROWSED ||
letter.assignmentStatus === SafetySources.QUESTIONS_COMPLETE
) {
if (
letter.form16 &&
letter.assignmentStatus !== SafetySources.QUESTIONS_COMPLETE
) {
f16Browsed.push(letter)
} else {
qcAndBrowsed.push(letter)
}
}
})
setQcAndBrowsed(qcAndBrowsed)
setF16Browsed(f16Browsed)
// begin sign all with first f16 letter
if (f16Browsed.length > 0) {
setSelectedLetter(f16Browsed[0])
} else {
setSelectedLetter(null) // clear any previous viewed letter
}
setModalVisible(false)
}
return [signAll]
}

Rxjs http chained request error handling

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()
)
)
)
};

Resources