I'm cancelling axios requests when the component unmounts as following
const CancelToken = axios.CancelToken
const source = CancelToken.source()
componentWillUnmount () {
source.cancel('message')
}
When the component is unmounted, the network requests happening in the background are flagged as 'stalled'. The problem is when the user goes to that component again, network requests supposed to happen in componentDidMount do no start.
axios#cancellation describes two ways to use the cancelToken. You used the first way, with source.token/source.cancel. I started out that way, and had a similar problem as yours: Once a request to a particular URL was canceled, I could never get a successful response from that URL again. I switched to the second method using an executor function and the problem went away. I guess was sharing the same cancelToken for multiple requests, which is what they say you can do with the executor function method. Anyway, maybe that would work for you too.
After canceling set a new token.
let uploadCancelTokenSource = axios.CancelToken.source();
upload() {
const config = {
cancelToken: uploadCancelTokenSource.token,
};
}
cancel() {
uploadCancelTokenSource.cancel('Operation canceled by the user');
uploadCancelTokenSource = axios.CancelToken.source();
}
Related
I'm building a basic http server (using only inside lan, to do some things) and for that im using node js. I've started my server and i wrote a basic callback function to respond to http requests. However, i wanted to see what would happen if the function i'm running took some time to conclude (like a db query, for example). So i've wrote this:
const http = require("http");
const router = require("../routes/router").router;
const config = require("../config/serverconf.json");
class HttpServer {
constructor() {
this.httpServer = http.createServer();
this.httpServer.on("request", (req,res)=>{return new Promise((resolve)=>{
setTimeout(()=>{
res.writeHead(200, {"Content-Type":"text/plain"});
res.write("Hello World!");
res.end();
},2000);
console.log("resolved");
console.log("--------")
resolve("ok");
})});
this.httpServer.listen(config.port, config.host);
}
}
(It was a little more complex, i tried to put it all in the same snippet to better show my problem).
From my understanding, when a request arrives, the callback function creates a new promise, that runs the function in the promise constructor. This function schedules the timeout callback, logs some stuff, then resolves the function. However, it seems that despite the function have already finished, when another request arrives, it waits for the timeout callback function to finish before scheduling the next request's timeout callback. (For example, if i were to get two requests "at the same time" one would take 2 seconds to finish, and the other would take 4). Am i thinking about it the wrong way? If so, how could i achieve something like title's idea. Thanks in advance!
Ok, so apparently, there was something wrong with my browser (using Google Chrome, but tried on MS Edge too). I changed my code a little bit and tried again. This time the response times were very inconsistent, but it seemed like it was kinda working. Took the advise from #Bergi and tried making the requests with curl and sure enough, it worked just fine (without the chunked encoding). Heres the new code:
HttpServer.js:
const http = require("http");
const router = require("../routes/router").router;
const config = require("../config/serverconf.json");
class HttpServer {
constructor() {
this.httpServer = http.createServer();
this.httpServer.on("request", async (request, response)=>{
let responseParams = await router.routeCall(request);
response.writeHead(responseParams.responseCode, responseParams.headers);
response.write(responseParams.body);
response.end();
});
this.httpServer.listen(config.port, config.host);
}
}
router.js:
class router{
static routeCall(url){
switch(url){
default:
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
"responseCode":200,
"body":"Message Resolved! Hello! :D",
"headers":{"Content-Type":"text/plain"}
});
}, 2000);
});
}
}
}
I would still like to know what is wrong with the first aproach, as i really dont know why it fails (at least on my end). Thanks again #Bergi :D
In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.
I'd like to execute one http get request after another has completed. The endpoint URL for the second request will need to depend on the first request.
I've tried nesting the requests with something like this in Angular:
this.http.get(endpoint1).subscribe(
success => {
this.http.get(endpoint2 + success).subscribe(
anotherSuccess => console.log('hello stackoverflow!')
);
}
);
First question here on stackoverflow, please let me know if I should provide more detail.
here you can find how to do that, you have various options:
with subscribe:
this.http.get('/api/people/1').subscribe(character => {
this.http.get(character.homeworld).subscribe(homeworld => {
character.homeworld = homeworld;
this.loadedCharacter = character;
});
});
with mergeMap
this.homeworld = this.http
.get('/api/people/1')
.pipe(mergeMap(character => this.http.get(character.homeworld)));
There is a version with forkJoin but isn't explicit compared with subscribe and mergeMap.
You should probably try using ngrx (redux) triggering the second request depending on a success action.
The flow should be something like this: dispatch an action from a component -> call the first request -> the request triggers a success action -> success effect triggers the second request with a payload from the previous request.
Read the docs here
For some reason, I cannot get testcafe's RequestLogger to log any of the API calls that I am making. I've read nearly all of the articles and questions on the RequestLogger and everything is pointing to what appears to be the same as the below code as working. Not sure what I'm doing wrong, any help would be great.
References:
https://devexpress.github.io/testcafe/documentation/test-api/intercepting-http-requests/logging-http-requests.html
Cannot intercept outgoing AJAX request from page using Testcafe
How to log Google Analytics calls in Testcafe?
I am running locally and hitting an API that is running locally as well, front-end on port 3000 and backend on port 8080, API is at: 8080/api/admin. I can see the logger as injected into the test but nothing updates it, its just a bland object with initial props and will error out after the t.expect statement.
I wonder if the beforeEach is breaking something but I need it in order to fire any API calls because the user needs to be authenticated. I can see the API request being called when debugging, that I am trying to intercept, but no luck
testcafe version: 1.0.0 || 0.23.3
Test code
// have tried several urls, from exact to generic to ports.
const logger = RequestLogger("/api/", {
logRequestHeaders: true,
logRequestBody: true
});
const url = 'localhost:3000/reports';
fixture `Report`
.page(url)
.requestHooks(logger)
.beforeEach(async (t: TestController) => {
await loginAndNavToReports({ t });
});
test("Reports", async (t: TestController) => {
// this fires an api call through the /api/ path
await t.click(".test-reportLaborSummary");
// have tried several comparisons here, all fail or expect falsey to be truthy errors
await t.expect(logger.count(() => true)).ok();
}
I suspect that TestCafe runs faster than the code that calls the api.
Before using the logger object you should wait that it has received at least one call.
To check if the logger has received a call, I suggest to do it this way:
await wait_for_first_request();
const receivedCalls = logger.requests.length;
if (receivedCalls === 0) {
throw new Error('api has not been called')
}
async function wait_for_first_request() {
for (let i = 0; i < 50; i++) {
await t.wait(100);
if (logger.requests.length > 0 ) {
return;
}
}
}
I've done a simple service-worker to defer requests that fail for my JS application (following this example) and it works well.
But I still have a problem when requests succeed: the requests are done twice. One time normaly and one time by the service-worker due to the fetch() call I guess.
It's a real problem because when the client want to save datas, they are saved twice...
Here is the code :
const queue = new workbox.backgroundSync.Queue('deferredRequestsQueue');
const requestsToDefer = [
{ urlPattern: /\/sf\/observation$/, method: 'POST' }
]
function isRequestAllowedToBeDeferred (request) {
for (let i = 0; i < requestsToDefer.length; i++) {
if (request.method && request.method.toLowerCase() === requestsToDefer[i].method.toLowerCase()
&& requestsToDefer[i].urlPattern.test(request.url)) {
return true
}
}
return false
}
self.addEventListener('fetch', (event) => {
if (isRequestAllowedToBeDeferred(event.request)) {
const requestClone = event.request.clone()
const promiseChain = fetch(requestClone)
.catch((err) => {
console.log(`Request added to queue: ${event.request.url}`)
queue.addRequest(event.request)
event.respondWith(new Response({ deferred: true, request: requestClone }))
})
event.waitUntil(promiseChain)
}
})
How to do it well ?
EDIT:
I think I don't have to re-fetch() the request (because THIS is the cause of the 2nd request) and wait the response of the initial request that triggered the fetchEvent but I have no idea how to do it. The fetchEvent seems to have no way to wait (and read) the response.
Am I on the right way ? How to know when the request that triggered the fetchEvent has a response ?
You're calling event.respondWith(...) asynchronously, inside of promiseChain.
You need to call event.respondWith() synchronously, during the initial execution of the fetch event handler. That's the "signal" to the service worker that it's your fetch handler, and not another registered fetch handler (or the browser default) that will provide the response to the incoming request.
(While you're calling event.waitUntil(promiseChain) synchronously during the initial execution, that doesn't actually do anything with regards to responding to the request—it just ensures that the service worker isn't automatically killed while promiseChain is executing.)
Taking a step back, I think you might have better luck accomplishing what you're trying to do if you use the workbox.backgroundSync.Plugin along with workbox.routing.registerRoute(), following the example from the docs:
workbox.routing.registerRoute(
/\/sf\/observation$/,
workbox.strategy.networkOnly({
plugins: [new workbox.backgroundSync.Plugin('deferredRequestsQueue')]
}),
'POST'
);
That will tell Workbox to intercept any POST requests that match your RegExp, attempt to make those requests using the network, and if it fails, to automatically queue up and retry them via the Background Sync API.
Piggybacking Jeff Posnick's answer, you need to call event.respondWith() and include the fetch() call inside it's async function().
For example:
self.addEventListener('fetch', function(event) {
if (isRequestAllowedToBeDeferred(event.request)) {
event.respondWith(async function(){
const promiseChain = fetch(event.request.clone())
.catch(function(err) {
return queue.addRequest(event.request);
});
event.waitUntil(promiseChain);
return promiseChain;
}());
}
});
This will avoid the issue you're having with the second ajax call.