Cancel previous promise - javascript

I am implementing a search input in my React app which may take several seconds for a response from the server based on the size of the result set. The issue I am having right now is if the user searches for a term which has a large result set and then searches for a term with a small result set straight after, the first search is returning after the last search and is overwriting the data, therefore displaying incorrect results on the table.
Currently I have a search component which calls an async function in its parent (this function can be called from several child components, Search being one of them). Inside the async function I have an await call to the service with the search query. Once that returns the results are passed to a function which updates some state.
I have read about cancel tokens but i'm not totally sure how to implement this. When the search component makes the initial call, there will be a promise which will be pending until the result is received. When I search again, how would I be able to ignore the result of the first promise? Each time I search, would I store the promise in a field of the component and somehow check this field in future searches?
I read many possible solutions to this online. I am using fetch-retry to handle the API call and would rather not use a library such as bluebird or axios. The main part I don't understand is how to have my promise not resolve if a promise has been created in future.
Hope I explained this well enough!
Thanks

When I search again, how would I be able to ignore the result of the first promise?
You probably don't want that, as youbare wasting some bandwith if you do a request and ignore its result. Instead you should cancel the underlying request (not the promise, promises can't be canceled directly).
To do so you could keep an abortion controller for each search:
this.controller = new AbortController();
Then launch all fetch requests as:
fetch(url, { signal: this.controller.signal })
.then(res => res.json())
.then(res => this.setState({ /*...*/ }))
.catch(() => /*..*/)
Now if you restart the search just do:
this.controller.abort();
this.controller = new AbortController();
and do the fetching again.
Read on

Related

React setState within Promise.all

I'm working on a project where we would like run multiple fetches in parallel to a very slow API. Ideally, we would like to populate our interface for the user as this data is received and do so in a summative manner. These requests may or may not resolve in the order that the API calls were made.
Most use cases of Promise.all with a setState involve setting state after all promises have resolved. However, what I'm looking to do demands setting state as a side effect within the child promises themselves, I believe.
So this is (simplified) what I am doing to achieve this:
const request = async (setState, endpoint) => {
const response = await fetch(endpoint);
const data = response.json();
setState(state => ({ ...state, ...data }))
}
// Called within React component as a side effect
const fetchAllData = (setState) => {
Promise.all(
[
request(setState, url_1),
request(setState, url_2),
request(setState, url_3)
]
)
}
Now, I'm running some testing and this does appear to work. I believe I should not be running into race conditions with the state because setState is being passed a function. However, I do wonder if I'm doing something dangerous with respect to React, updating state, and rendering.
Is there anything wrong with this picture?
There's nothing wrong with immediately updating state from each individual promise; this will work just fine. You might have a race condition if every request attempts to update the same bit of data, but as long as they write different parts of your state you should be fine (the state updater callback pattern is necessary though).
The only thing wrong with your code is the missing error handling, and that the Promise.all is currently a bit superfluous.

Firebase .update() returning undefined, is it not supposed to return the updated object?

I have a simple update Firebase function, which looks like the following:
export default async function updateStuff(firestore, id, data) {
const stuff = await firestore
.collection('Stuff')
.doc(id)
.update(data)
console.log('updated stuff:', stuff) // Will always be undefined, despite working.
}
The update is working as expected and everything, however, once the Promise goes through all I have is undefined. I could have sworn that it once returned the actual entry that was updated with its updated values, does it not do so anymore?
Is there something that could be done to verify that it has been in fact updating without doing a whole separate .get() call after the update?
I can't find the proper update() API docs, but I did as stated on this page:
To know when your data is committed to the Firebase Realtime Database server, you can use a Promise. Both set() and update() can return a Promise you can use to know when the write is committed to the database.
Is it just a Promise<void> with no other information on the updated object? And I can assume it will throw if it fails? I just seems counter-productive for an update API to have to keep refetching values once the operation is done.
If you look at the reference documentation for DocumentReference.update(), you'll see that it returns Promise<void>.
If you need the updated data, you'll need to get() that in a separate call. If you update multiple documents, you may need multiple get() calls too.
The promise will indeed be rejected when the operation fails.

AngularFire firestore get / snapshotchanges / valuechanges action on observable is not async?

Hi I am having a very strange behaviour.
I am iterating over some documents and setting some promises that when the documents are fetched the UI is updated.
However, while the promises are atomic, the firestore / AngularFire waits for all the promises.
Example:
for (const event of events) {
this.eventService.getEventActivitiesAndSomeStreams(this.user,
event.getID(),
[DataLatitudeDegrees.type, DataLongitudeDegrees.type])
.pipe(take(1)).toPromise().then((fullEvent) => {
this.logger.info(`Promise completed`)
})
}
One would expect that slowly for each promise as the data comes it would print the promise completed.
However they are all printed as once. It doesn't look that those promises come one by one but "all at once". There is a big waiting time till the first console log is printed and then all promises print that.
So I would expect if I have a progress bar to increase little but little but increases at once
The inner call this.eventService.getEventActivitiesAndSomeStreams
return this.afs
.collection('users')
.doc(userID)
.collection('events')
.doc(eventID)
.collection('activities')
.doc(activityID)
.collection('streams', ((ref) => {
return ref.where('type', 'in', typesBatch);
}))
.get()
.pipe(map((documentSnapshots) => {
return documentSnapshots.docs.reduce((streamArray: StreamInterface[], documentSnapshot) => {
streamArray.push(this.processStreamDocumentSnapshot(documentSnapshot)); // Does nothing rather to create an class of the JSON object passed back from the firestore
return streamArray;
}, []);
}))
Now, if I put an await inside the for loop of course this works as it should going and completing the promises as it should, but then it takes a lot of time.
I also tried to not use AngularFire and use the native JS SDK with the same effect.
I am suspecting that the IndexedDB can be causing this or some other Firebase logic.
What am I missing here, and how can I have the desired behaviour if possible?
You could repro this via a ["users" -> "events" -> "something"] firestore collections, were each "user" has lets say 500 "events" and each of those events has 2 more docs.
So get all the events for the user and try to make for each one a promise that will return 2 documents of "something" inside a for array )
This behavior is pretty expected and has nothing at all to do with firebase. you're iterating over an array and sending out requests. there is no waiting or delay between items, so the for loop (without await statements) will finish in an imperceptibly small amount of time, which means all of the requests are being sent out within milliseconds of each other, or basically at the same time. So their responses should be expected to arrive at basically the same time as well.
You've stated that you don't want to use await statements and iterate one by one, so it's tough to know exactly what you do want or expect to happen. maybe you want them to be spaced .5 second apart? If so, you need to write that logic:
timer(0, 500).pipe( // put whatever ms time between requests you want here?
take(events.length),
switchMap(i => {
return this.eventService.getEventActivitiesAndSomeStreams(this.user,
events[i].getID(),
[DataLatitudeDegrees.type, DataLongitudeDegrees.type]).pipe(take(1))
})
).subscribe(fullEvent => {
this.logger.info(`Promise completed`)
})
(removed promises cause idk why they're being used in the first place and this kind of control is easier with rxjs IMO)

React SetState Race Condition using Promise.all

componentDidMount() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
OfferCreationActions.updateOfferCreation('viewOnly', true);
OfferCreationActions.updateOfferCreation('loadingOffer', true);
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
// let state settle before loading
setTimeout(() => {
OfferCreationActions.loadOffer(this.props.match.params.offerId);
}, 1500);
});
});
}
I'm working on a React app that needs to preload some data into state then load a larger object that references the preloaded data to map some fields. I've ran into a race condition in which some of the data from the promise chain is still being processed when I try to do the mapping. I added in the timeout yesterday but that doesn't feel like the best solution to me. I'm still fairly new to React and we are using Reflux as the store (if that makes a difference). Is there a better way to ensure all the data from the promise is currently being reflected in the state prior to making the call? Should I hook into componentShouldUpdate and check all of the fields individually?
There is a fundamental flaw with the way this is implemented! You are breaking the principle of uni directional data flow. Here are a few suggestions to fix it.
Do your side effect handling inside a seperate overarching function.
Handling promise race condition is a side effect(Something that is outside of React's UniFlow). So this is not a problem linked to "React". So as the first step onComponentDidMount delegate this race condition logic to a seperate action. Possibly do it inside "resetOfferOverall()" which is actually what is happening I guess.
Manage the promise inside the action and dispatch payloads to the store
In your code you are guaranteed that the "then" will be executed after the promise is resolved. Howerver the 2 calls to "updateOfferCreation" do not fall under this contract since it's outside the promise.all. May be they also need to come inside the massive promise.all section? Maybe they need to be completed before running the massive section. Just recheck this!
resetOfferOverall() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
.then( () => {
// These are not guaranteed to be completed before the next "then" section!
OfferCreationActions.updateOfferCreation('viewOnly', true);
OfferCreationActions.updateOfferCreation('loadingOffer', true);
//*****************************************
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
OfferCreationActions.loadOffer(offerId);
});
});
}
If you want this sections to be completed before getting into that
massive promise all, change your code as follows.
async resetOfferOverall() {
Promise.all([OfferCreationActions.resetOffer()]).then( () => {
.then( () => {
await OfferCreationActions.updateOfferCreation('viewOnly', true);
await OfferCreationActions.updateOfferCreation('loadingOffer', true);
//await will stop code execution until the above async code is completed
Promise.all([
OfferCreationActions.loadBarcodes(),
OfferCreationActions.loadBrandBarcodes(),
OfferCreationActions.loadBrands(),
OfferCreationActions.loadPayers(),
OfferCreationActions.loadSegments(),
OfferCreationActions.loadTactics(),
OfferCreationActions.loadStates(),
]).then(() => {
//Now JS Guarantees that this call will not be called until everything above has been resolved!
OfferCreationActions.loadOffer(offerId);
});
});
}
Make sure that the actions you are waiting are returning a promise
Whatever pomises you wait on, if you do not actually return the relevant promise that is within the call itself, your code will not work properly.Let's consider the load barcodes action and Let's assume you use axios to fetch data.
loadBarcodes(){
// This "return" right here is vital to get your promises to behave properly
return axios.get('https://localhost:8080/api/barcodes/').then((response) =>{
//DISPATCH_TO_STORE
});
//If you did not return this promise this call will resolve immediately
}
On your component watch for the relevent Store. Show a loader until the payload is loaded to the store.
As you can see by relying on a store update to show the data we do not break the unidirectional data flow.

How store AngularJS $resource calls in an array to execute all together after?

I'm working with AngularJS 1.5 (I'm really beginner with JS) in a user profile view where the user can change a lot of things in his data.
In this view the data is split in several sections and save all of this data means to do several calls to server ( due to the design of app). So the problem that I found is that when the user modify his data maybe modify only a part of this and when it push save button I don't want call all methods, only to necessary methods.
I've programed the way to detect the changes in data blocks when the user push the save button, sometimes the controller make a call and in other cases two or three. But the problem is that the calls (made with $resource library) is executed asyncronously and I would like can control this better.
I would like do the next: store all calls in a list or array but wihout execute them and after execute all at the same time (more or less). If any of this fails I would like show a error message to user (only a generic error message), but internally log the call that failed, and in the same way only show (and only one) a success message when all calls have ended with success ( and not a message per success call).
I don't know how to do this, some mates say me that maybe I need use $q AngularJS service to do this, or store the promises that $resource have to execute all after (I've trying this without success) or work with promises in JS.
Anyone can give me any idea?
Finally I resolved my problem using $q. At first I wanted store the calls without execute them (I thought that it was better) but finally I can check that only stored the results of this calls is enought for my aim. So, this a skeleton of the solution that I've been done:
At the beginning of the controller
var promises = [];
In all places where I need make a controlled call inside of save user data function:
var deferred = $q.defer();
var promise = vm.teacher.$update(
function () { // Success
deferred.resolve('Success updating the teacher.');
},
function (error) { // Fail
deferred.reject('Error updating the teacher, error: ' + error)
});
promises.push(deferred.promise)
}
...
... vm.otherItems.$update ...
...
And at the end of this function, something like this:
$q.all(promises).then(
function(value){
console.log('Resolving all promises, SUCCESS, value: ')
console.log(value);
toastService.showToast('Success updating the teacher.');
// It deleted old promises for next iteration (if it exists)
promises = [];
},function(reason){
console.log('Resolving all promises, FAIL, reason: ')
console.log(reason);
toastService.showToast('Fail updating the teacher.');
}
)
Thanks for the help!

Categories

Resources