Ignoring failed promises in Promoise.all - reactjs

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

Related

Delete data in state React Hooks

Maybe it is a dumb question, because I don't find any response on the net for this, but I'm trying to update my state after a axios.delete.
When I add data, I do this and it works fine :
const handleAddIncome = () => {
let incomeName = document.getElementById('newIncomeName').value
let incomeAmount = document.getElementById('newIncomeAmount').value
let data = {
[incomeName]: parseInt(incomeAmount)
}
axios({
method: "put",
url: `http://localhost:5000/api/balance/${uid}`,
withCredentials: true,
data: { incomes: { ...userWalletIncomes, ...data } }
}).then(() => {
setUserWalletIncomes({ ...userWalletIncomes, ...data })
})
}
I also added a bouton that delete the key/value, and this is where I'm stuck.
Here is what I tried, but no success :
const handleDeleteIncome = (key) => {
let data = { [key]: "" }
axios({
method: "delete",
url: `http://localhost:5000/api/balanceOneIncome/${uid}`,
data: data
}).then(() => {
data[key] = null
setUserWalletIncomes(...userWalletIncomes, data)
})
}
PS : the axios delete works fine, my database is updated normally. Not the state
Thanks for help !
[EDIT]
Here is my UseEffect :
useEffect(() => {
if (uid !== null) {
axios.get(`http://localhost:5000/api/balance/${uid}`)
.then((res) => {
if (res.data[0].incomes && res.data[0].fees) {
setUserWalletIncomes(res.data[0].incomes)
setUserWalletFees(res.data[0].fees)
} else if (res.data[0].incomes && !res.data[0].fees) {
setUserWalletIncomes(res.data[0].incomes)
setUserWalletFees({ 'cliquez-ici': '' })
} else if (!res.data[0].incomes && res.data[0].fees) {
setUserWalletIncomes({ 'cliquez-ici': '' })
setUserWalletFees(res.data[0].fees)
}
})
}
}, [uid])
For those who need, I finally chose to change my controller in my backend and make it send the new object after the delete.
I just take that response, and set the new state.
Thanks
Because you already have setUserWalletIncomes({ ...userWalletIncomes, ...data }), I expect userWalletIncomes is an object like { name1: value1, name2: value2 }, right?
Then you have setUserWalletIncomes(...userWalletIncomes, data), use array spread on an object is a runtime error because it's not iterable:
let a = { foo: 42 };
console.log(...a); // TypeError: Found non-callable ##iterator
I guess you still wanted to write this:
let data = { [key]: "" };
setUserWalletIncomes({ ...userWalletIncomes, ...data });
But this only sets the key field to an empty string. To remove the field, you can combine the answer from How do I remove a property from a JavaScript object?
const temp = {...userWalletIncomes};
delete temp[key];
setUserWalletIncomes(temp);

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

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

ReactJS: Check if array contains value else append

I'm trying to check if a JSON response contains a value already inside an array and if it doesn't add it in. The problem I'm having is understanding how to approach this in reactjs. I'm checking before I append it but it doesn't want to work. I've tried passing in user object & user.id but these fail. The attempt below fails to compile but it should help understand what I'm trying to achieve.
Code:
componentWillMount() {
fetch('http://localhost:8090/v1/users')
.then(results => {
return results.json();
})
.then(data => {
data.map((user) => (
if(userList.hasOwnProperty(user.id)) {
userList.push({label: user.title, value: user.id})))
}
})
}
map return the resultant array, but you are not returning anything from it, you should instead use forEach Also you need to check if the userList array contains the id, for that you can use findIndex
What you need is
state = {
userList: [];
}
componentDidMount() {
fetch('http://localhost:8090/v1/users')
.then(results => {
return results.json();
})
.then(data => {
const newUserList = [...this.state.userList];
data.forEach((user) => { // use { here instead of
if(userList.findIndex(item => item.value === user.id) < 0) {
newData.push({label: user.title, value: user.id})
}
})
this.setState({userList: newUserList});
});
}
render() {
return (
{/* map over userList state and render it here */}
)
}
I'd recommend using reduce to turn the returned data into an array you'd like, then adding those values to your existing user list:
fetch('http://localhost:8090/v1/users')
.then(res => res.json())
.then(data => data.reduce((acc, user) => {
const idList = userList.map(user => user.id);
if (idList.indexOf(user.id) === -1) {
acc.push({label: user.title, value: user.id})
}
return acc;
},[]))
.then(newList => userList = [...userList, ...newList]);

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