Cancel request if there is new one(promises) - javascript

I send request to server everytime user types something. I use debounce for the 400ms delay:
type = debounce((text) => {
this.props.actions.loadInfo(text)
}, 400);
When I type something, stop and start again and repeat it, several requests are send and I receive irrelevant data. I use promises:
export const loadInfo = (text) => dispatch => {
loadData(text).then(result => {
dispatch(showUserData(result));
});
};
export const loadData = async (text) => {
const tabData = await axios.get(`url&query=${text}`);
return tabData;
}
I need somehow cancel previous request if user sends the new one(when he typed something), what is the best way to do that? I expected debounce will help me but not. I use axios. This is not duplicate of questions here, I checked provided solutions but thet don't help me

The problem is similar to this one. Axios cancellation API can be used to cancel old requests. This should be done in a function that does a request (loadData) and has direct access to Axios, it may be also debounced:
let cancelObj;
export const loadData = debounce((text) => {
if (cancelObj) {
this.cancelObj.cancel();
}
cancelObj = CancelToken.source();
return axios.get(`url&query=${text}`, {
cancelToken: this._fetchDataCancellation.token
}).catch(err => {
// request wasn't cancelled
if (!axios.isCancel(err))
throw err;
});
}, 200);
Since Redux is used, other solutions may involve it, they depend on how Redux is used.

Even I tried to use debounce function in my code but the problem is that if user types very fast stop and then again start typing, in that case, your input values get updated and UI get distorted, to avoid this I used XMLHttpRequest and its abort() to cancel the previous calls, if calls do not succeed then it will be canceled,
you can try this solution, https://stackoverflow.com/a/55509957/9980970

Related

How to tell React Query fetchQuery to make a new GET request and not used the already cached response?

I have the following function that makes a GET request for my user information and caches it using react query's fetchQuery so that every call after the first one does not make a GET request and instead just pulls the data from the cache.
export const getVegetables = async () =>
await queryClient.fetchQuery(['getVegetables'], async () => {
try {
const { data } = await request.get('/vegetables');
return data;
} catch (error) {
throw new Error('Failed to fetch vegetables');
}
});
The problem is that now I actually want to make a new GET request in order to check if the user data has changed, but calling getVegetables() pulls from the cache. How can I instruct fetchQuery to make a fresh GET request and not used the cache?
A slight modification to your function will allow you to first invalidate the query (which will remove it from the cache).
export const getSelf = async (skipCache = false) => {
if(skipCache) { queryClient.invalidateQueries(['getSelf']); }
return queryClient.fetchQuery(['getSelf'], async () => {
try {
const { data } = await request.get('/users/me');
// #todo: This sideloads a bunch of stuff, that we could cache
return data;
} catch (error) {
throw new Error('Failed to fetch user information');
}
});
}
In case of using fetchQuery, you can set cacheTime to 0 in query options, so every time you call it, it will suggest that cache is outdated and fetch fresh data, but I'd suggest you to use useQuery.
Here you can read about difference between useQuery and fetchQuery
The best way is to use useQuery hook and invalidate that query.
import { useQueryClient } from '#tanstack/react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['getSelf'] })
After invalidation, it will immediately fetch fresh data.
fetchQuery will always fetch unless there is data in the cache that is considered fresh. This is determined by the staleTime setting.
staleTime defaults to 0 - which means "immediately stale". So the code you are showing that is calling fetchQuery should always fetch - unless you have a global staleTime set. You're not showing this in your code, but I guess this must be the reason. Note that fetchQuery doesn't know about staleTime being set by other observers (created by useQuery).
Now if you have a globally set staleTime and that is affecting fetchQuery, but you still want to always fetch, the best thing you can do is pass staleTime: 0 directly to fetchQuery. Again, this is the default behaviour, so it's only necessary if you have a global staleTime set:
await queryClient.fetchQuery(
['getSelf'],
async () => { ... },
{ staleTime: 0 }
)

How can I use AbortController in Next js?

My application allows users to do searches and get suggestions as they type in the search box. For each time that the user enters a character, I use 'fetch' to fetch the suggestions from an API. The thing is that if the user does the search fast, he can get the result before the suggestions are fetched. In this case, I want to cancel the fetch request.
I used to have the same application in React and I could easily cancel the request using AbortController, but that isn't working in Next js.
I did some research and I think the problem is happening because Next doesn't have access to AbortController when it tries to generate the pages.
I also had this problem when I tried to use 'window.innerWidth' because it seems Next doesn't have access to 'window' either.
The solution I found was to use 'useEffect'. It worked perfectly when I used it with 'window'.
const [size, setSize] = useState(0)
useEffect(() => {
setSize(window.innerWidth)
}, [])
But it isn't working when I use AbortController. First I did it like this:
let suggestionsController;
useEffect(() => {
suggestionsController = new AbortController();
},[])
But when I tried to use 'suggestionsController', it would always be undefined.
So I tried to do the same thing using 'useRef'.
const suggestionsControllerRef = useRef(null)
useEffect(() => {
suggestionsControllerRef.current = new AbortController();
},[])
This is how I'm fetching the suggestions:
async function fetchSuggestions (input){
try {
const response = await fetch(`url/${input}`, {signal: suggestionsControllerRef.current.signal})
const result = await response.json()
setSuggestionsList(result)
} catch (e) {
console.log(e)
}
}
And this is how I'm aborting the request:
function handleSearch(word) {
suggestionsControllerRef.current.abort()
router.push(`/dictionary/${word}`)
setShowSuggestions(false)
}
Everything works perfectly for the first time. But if the user tries to do another search, 'fetchSuggestions' function stops working and I get this error in the console 'DOMException: Failed to execute 'fetch' on 'Window': The user aborted a request'.
Does anyone know what is the correct way to use AbortController in Next js?
The solution I found to the problem was create a new instance of AbortController each time that the user does the search. While the suggestions were being displayed, 'showSuggestions' was true, but when 'handleSearch' was called, 'showSuggestions' was set to false. So I just added it as a dependency to useEffect.
useEffect(() => {
const obj = new AbortController();
setSuggestionController(obj)
},[showSuggestions])
I also switched from useRef to useState, but I'm not sure if that was necessary because I didn't test this solution with useRef.
I don't know if that is the best way of using AbortController in Next js, but my application is working as expected now.
I suppose you can try an abort controller to cancel your requests if the user stops typing, but this is not the standard way of solving this common problem.
You want to "debounce" the callback that runs when the user types. Debouncing is a strategy that essentially captures the function calls and "waits" a certain amount of time before executing a function. For example, in this case you might want to debounce your search function so that it will only run ONCE, 500 ms after the user has stopped typing, rather than running on every single keypress.
Look into debouncing libraries or write a debounce function yourself, but fair warning it can be pretty tricky at first!

How do I deal with multiple setState called in the wrong order?

Let’s imagine a simple component run where the user clicks on a button that shows a popup (setState(P)) and that causes both an HTTP (setState(H)) and WebSocket (setState(W)) server to respond with other data to be shown in the popup. Note that onClick, onHTTPResponse and onWSResponse are simple prop event handlers passed to the component which call setState:
onClick: --setState(P)-->
onHTTPResponse: ---setState(H)-->
onWSResponse: ----setState(W)-->
Now let’s imagine another run where WebSocket is faster than the HTTP response:
onClick: --setState(P)-->
onHTTPResponse: ----setState(H)-->
onWSResponse: ---setState(W)-->
Problem with this run is that the WebSocket setState(W) depends on the data returned by the HTTP response, hence it depends on setState(H) — setState(W) must occur after setState(H).
How would I deal with such situation?
Edit: it seems from the responses that I didn’t explain my problem clearly. onHTTPResponse and onWSResponse handlers are called with the response of each. This is the API I have available. I need to solve the setState call order from the usage perspective.
Note also that the idea of these prop handlers is that they’re called at unpredictable times. The same example could be made with subscribing to an event emitter for whichever data. So answers that involve async/await or promises don’t really make sense: I’m dealing with an event emitter kind of API.
Why don't you call them in the same order that you described?
If sending a message via WebSocket needs data from HTTP request you can wait for the request from HTTP and call send a message via Websocket afterwards.
Just an example;
import React, { useEffect, useState } from "react";
const exampleSocket = new WebSocket(
"wss://www.example.com/socketserver",
"protocolOne"
);
export default function App() {
const [httpData, setHttpData] = useState(null);
const [socketData, setSocketData] = useState(null);
const onClick = () => {
fetchData();
};
const fetchData = () => {
fetch("yourAPI")
.then(response => {
// Examine the text in the response
response.json().then(function(data) {
sendMessage(data);
setHttpData(data);
});
})
.catch(err => {
console.log("Fetch Error :-S", err);
setHttpData(err);
});
};
const sendMessage = data => {
exampleSocket.send(data);
};
useEffect(() => {
exampleSocket.onmessage = evt => {
// listen to data sent from the websocket server
const message = JSON.parse(evt.data);
setSocketData(message);
};
}, []);
return (
<div className="App">
<button type="submit" onClick={onClick}>
Send request
</button>
<div>{JSON.stringify(httpData)}</div>
<div>{JSON.stringify(socketData)}</div>
</div>
);
}
use setState callback
this.setState(p, () => this.setState(H, () => this.setState(W)))
setState callback will be triggered with updated state values, so no need to use async/await.
You can use aysnc await function with await in front of onHTTP function and onWebSocket function. Note that you need to put the code you want to run first above the one you want to run later. Example,
await onHTTP()
await onWebSocket()
They second one will only run when the first one is successful.
Since my question was rather unclear I decided to post a more generic one without using React (but pure JavaScript). It seems that using observables is a way to solve this problem. More details are posted in this other question: How to deal with race conditions in event listeners and shared state?

Sleep function for React-Native?

So I'm trying to fetch all 'places' given some location in React Native via the Google Places API. The problem is that after making the first call to the API, Google only returns 20 entries, and then returns a next_page_token, to be appended to the same API call url. So, I make another request to get the next 20 locations right after, but there is a small delay (1-3 seconds) until the token actually becomes valid, so my request errors.
I tried doing:
this.setTimeout(() => {this.setState({timePassed: true})}, 3000);
But it's completely ignored by the app...any suggestions?
Update
I do this in my componentWillMount function (after defining the variables of course), and call the setTimeout right after this line.
axios.get(baseUrl)
.then((response) => {
this.setState({places: response.data.results, nextPageToken: response.data.next_page_token });
});
What I understood is that you are trying to make a fetch based on the result of another fetch. So, your solution is to use a TimeOut to guess when the request will finish and then do another request, right ?
If yes, maybe this isn't the best solution to your problem. But the following code is how I do to use timeouts:
// Without "this"
setTimeout(someMethod,
2000
)
The approach I would take is to wait until the fetch finishes, then I would use the callback to the same fetch again with different parameters, in your case, the nextPageToken. I do this using the ES7 async & await syntax.
// Remember to add some stop condition on this recursive method.
async fetchData(nextPageToken){
try {
var result = await fetch(URL)
// Do whatever you want with this result, including getting the next token or updating the UI (via setting the State)
fetchData(result.nextPageToken)
} catch(e){
// Show an error message
}
}
If I misunderstood something or you have any questions, feel free to ask!
I hope it helps.
try this it worked for me:
async componentDidMount() {
const data = await this.performTimeConsumingTask();
if (data !== null) {
// alert('Moved to next Screen here');
this.props.navigator.push({
screen:"Project1.AuthScreen"})
}
}
performTimeConsumingTask = async() => {
return new Promise((resolve) =>
setTimeout(
() => { resolve('result') },
3000
)
);
}

What is the expected behaviour Cancelling async request in Redux-observable using takeUntil

i am new to RXJS, i found Redux-observable canceling async request using takeUntil is very useful. but while i was testing it i found that the actual request is still going on even though we cancel the request..
i have this JSbin code snippet to test.
https://jsbin.com/hujosafocu/1/edit?html,js,output
here the actual request is not canceling, even if you cancel the request by clicking the cancel (multiple times) button.
i am not sure this is how it should be.. if yes, then what does it meant by canceling async request. I am bit confused.. Please share some thoughts..
any respond to this will greatly appreciate.. thanks
The issue is very subtle, but obviously important. Given your code:
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.delay(2000) // <-- while we're waiting, there is nothing to cancel!
.mergeMap(action =>
Observable.fromPromise(
jQuery.getJSON('//api.github.com/users/redux-observable', data => {
alert(JSON.stringify(data));
})
)
.map(fetchUserFulfilled)
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
);
The kicker is the .delay(2000). What this is saying is, "don't emit the action to the rest of the chain until after 2000ms". Because your .takeUntil(action$.ofType(FETCH_USER_CANCELLED)) cancellation logic is inside the mergeMap's projection function, it is not yet listening for FETCH_USER_CANCELLED because there is nothing to cancel yet!
If you really want to introduce an arbitrary delay before you make the ajax call, but cancel both the delay OR the pending ajax (if it reaches there) you can use Observable.timer()
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
Observable.timer(2000)
.mergeMap(() =>
Observable.fromPromise(
jQuery.getJSON('//api.github.com/users/redux-observable', data => {
alert(JSON.stringify(data));
})
)
.map(fetchUserFulfilled)
)
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
);
I imagine you don't really want to introduce the arbitrary delay before your ajax calls in real-world apps, in which case this problem won't exist and the example in the docs is a good starting reference.
Another thing to note is that even without the delay or timer, cancelling the ajax request from your code doesn't cancel the real underlying XMLHttpRequest--it just ignores the response. This is because Promises are not cancellable.
Instead, I would highly recommend using RxJS's AjaxObservable, which is cancellable:
Observable.ajax.getJSON('//api.github.com/users/redux-observable')
This can be imported in several ways. If you're already importing all of RxJS a la import 'rxjs';, it's available as expected. Otherwise, there are several other ways:
import { ajax } from 'rxjs/observable/dom/ajax';
ajax.getJSON('/path/to/thing');
// or
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/dom/ajax';
Observable.ajax.getJSON('/path/to/thing');
It's important to remember, like all the Observable factories, Observable.ajax is lazy meaning it does not make the AJAX request until someone subscribes to it! Where as jQuery.getJSON makes it right away.
So you can put it together like this:
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
Observable.timer(2000)
.mergeMap(() =>
Observable.ajax.getJSON('//api.github.com/users/redux-observable')
.do(data => alert(JSON.stringify(data)))
.map(fetchUserFulfilled)
)
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
);
A working demo of this can be found here: https://jsbin.com/podoke/edit?js,output
This may help someone in future..
Like jayphelps mentioned above the better solution is using RXjs AjaxObservable, because it is canceling the actual XMLHttpRequest rather than neglecting the responds.
But currently there is some issues going on RxJS v5 "RxJS Observable.ajax cross domain issue"
good solution i found is "allows bypassing default configurations"
like below:
const fetchUserEpic = action$ =>
action$.ofType(FETCH_USER)
.mergeMap(action =>
Observable.timer(2000)
.mergeMap(() =>
Observable.ajax({
url:`//api.github.com/users/redux-observable`,
crossDomain: true
})
.do(data => alert(JSON.stringify(data)))
.map(fetchUserFulfilled)
)
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
);
https://github.com/ReactiveX/rxjs/issues/1732

Categories