Chrome Tab duplicate not making network request - javascript

I have 3 api calls on application load (componentDidMount).
When I reload the page or open the new tab, all the api calls are made but on duplicating the tab only one of them is fired.
How do I trigger all the api calls on page duplication
Note:
Browser: chrome.

use Memopromise tools its an effective tool to handle asynchronous processes.
class MemoPromise {
constructor(getPromise) {
this.cache = {};
this.getPromise = getPromise;
this.request = this.request.bind(this);
}
request({ uniqueKey, ...rest }) {
if (!uniqueKey) {
return Promise.reject(new Error('Unique key not passed'));
}
if (this.cache[uniqueKey]) {
return this.cache[uniqueKey];
}
const promise = this.getPromise(rest);
this.cache[uniqueKey] = promise
.then((res) => {
delete this.cache[uniqueKey];
return res;
})
.catch((err) => {
delete this.cache[uniqueKey];
throw err;
});
return this.cache[uniqueKey];
}
}
In the above example, I have created a MemoPromise class whose instantiated object can memorize the promises returned by the passed function in the constructor till the time they are not resolved or rejected.
see execution code
const memoPromise = new MemoPromise(fn);
// invocation
const { request } = memoPromise;
request({ uniqueKey: url, apiType, url, payload });
// not required now
// fn({ apiType, url, payload });
after integrating the Memopromise class your browser you might not be getting face any problem with your API requests. it deals effectively with your duplicate requests.

Related

fetch calls in react world: ReactJS

There is a requirement of cancelling the request calls when navigating away from the page or when the same api call is made multiple calls ( keeping the last one active).
This is how the API is extracted out( just a high level)
AJAX.ts
export async function customAjax(options){
let options = {};
options.headers = { ...options.headers, ...obj.headers };
const response = await fetch(url, options);
await response.json()
}
GET and POST calls are being extracted as
API.ts
const get = (url, extra = {}) => request({ url, type: "GET", ...extra });
const post = (url, payload, extra = {}) => request({ url, data: payload ,type: "POST",
}, ...extra });
In the react component I call these utilities as follows:
function MyComponent(){
useEffect(() => {
makeCall();
}, []);
async function makeCall(){
const { response, error } = await API.post(URL, payload);
// Handling code is not added here
// In the similar fashion GET calls are also made
}
}
I have come across Abortcontroller to cancel request where we could use abort method during unmounting of the component.
Is there a way to do this at a utililty level, may be inside customAjax so that I could avoid writing abort controller code everywhere?
From my understanding... What you describe is no different than a memory leak issue. And the current method for avoiding memory leaks is with the AbortController().
As far as handling this at the "utility level", I don't think this is feasible, and indeed would go against the preferred notion of an api being unaware of what's going on at the React component level; i.e separation of concerns..
So, in order to accomplish your requirement, you'll need to use AbortController(), or a custom implementation using a boolean flag that reflects whether the component is mounted, on a per component basis.
Using the boolean flag, you may be able to accept an argument in your api, passing the flag as a parameter; but again, I think this would be considered an anti-pattern.
I understand you're looking for a minimal implementation; but standard practice is fairly minimal:
useEffect(() => {
let abortController = new AbortController();
// Async code
return () => { abortController.abort(); }
}, []);
Using a boolean flag would be more verbose, and would entail something like this in your case:
useEffect(() => {
let isMounted = true;
customAjax(isMounted);
return () => {
isMounted = false;
}
}, []);
To handle out-of-order ajax responses, you can use a local variable inside the effect. For example,
useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
if (!ignore) setProduct(json);
}
fetchProduct();
return () => { ignore = true };
}, [productId]);
The ignore variable will ensure that only the latest request's response is updated to state. Reference - https://reactjs.org/docs/hooks-faq.html#performance-optimizations
Regarding memory leak concerns, please see this discussion - https://github.com/reactwg/react-18/discussions/82

How to make Javascript wait for a Promise before sending a request?

I know there are many questions and answers based on Promise, but what I want to do is to retrieve some data with axios (to a microservice) and then to use this data in order to send another request (to a different microservice).
Somehow, I figured out how to set my request:
screenshot from console with the request right before axios call
The problem is that in backend I have only first two clauses. I think this is because I have used async/await in order to successfully avoid Promise and get the actual result/class. What I meant is that, maybe the request is sent before the promise is fulfilled, but how do I correctly get the request in console?
I am newbie into Javascript, so any helping hand is welcome.
EDIT:
Here is my code:
getServicesList = async (instanceIds) => {
return await FlowsInventoryAPI.searchServices(instanceIds, this.props.salesline, this.props.environment, this.props.sources, this.props.targets)
.then((response) => {
return response;
})
.catch((error) => {
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
}
The above one is the first call I've talked about.
buildSearchObject = (size, page, status) => {
let interval = TimestampUtils.getInterval(this.props.logsTimeInterval);
let from = interval.from * 1000;
let to = interval.to * 1000;
return {
fromMillis: from,
toMillis: to,
size: size,
page: page,
salesline: this.props.salesline,
environment: this.props.environment,
routes: this.props.routes,
sources: this.props.sources,
targets: this.props.targets,
customFilters: [...this.props.filters.values(), ...this.getEnabledAdvancedFilters().values()],
status: status === LogStatus.ALL ? "" : status,
sortFieldName: this.props.sortColumn,
order: this.props.sortOrder,
searchFilter: this.props.searchFilter,
searchFilterOperator: this.props.searchFilterOperator,
applications: this.props.applications,
openedStores: this.props.openedStores,
servicesPromise: this.state.servicesList // here is the promise
}
};
searchLogs = (size, page, status, callback) => {
loadingService.showLoadingModal("loadingLogsPage", this.props.location.pathname);
let searchObject = this.buildSearchObject(size, page, status);
ElasticSearchApi.search(searchObject, this.props.token)
.then(response => {
callback(response);
})
.catch((error) => {
loadingService.hideLoadingModal("loadingLogsPage", this.props.location.pathname);
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
};
I have the second call in last paragraph which calls the buildSearchObject method which contains our promise. As I told you I figured out how to send it as value, but I think that because of "asynchronicity" maybe my promise is not ready yet in the moment when second call is called, this is why my code has the promise in state.
EDIT 2:
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
Here is my constructor, where I create my this.state.servicesList.
Some advice:
Do not mix traditional promises syntax with async / await. It will make your code hard to understand, even for yourself. Do not mix either callback approach with promises. Choose one approach and stick to it.
If you are having a hard time with promises, force yourself to use async / await everywhere. async / await is easier to understand in my opinion, because it doesn't break your code flow.
For instance, transform this:
FlowsInventoryAPI.searchServices(/* params */)
.then((response) => /* response code */)
.catch((error) => /* error code */)
to:
try {
const response = await FlowsInventoryAPI.searchServices(/* params */);
/* response code */
} catch (error) {
/* error code */
}
Do not make your constructors asynchronous like you do where you call this.getServicesList, because you cannot wait for an asynchronous operation (like getServicesList) inside a constructor. Use instead a static async method.
For instance, transform this:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
}
to:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = { servicesList: null };
}
async init() {
this.state.servicesList = await this.getServicesList(this.getInstanceIds());
}
static async create(props) {
const someObject = new SomeObject(props);
await someObject.init();
return someObject;
}
}
Instead of calling const object = new SomeObject(props);, do const object = await SomeObject.create(props);
You will need to use await keyword to wait for a promise response before continuing.
// 1. Wait for create or update the customer before continuing
const customerId = await updateOrCreateCustomer(customerData);
// 2. Register sale, with customer created in previous section
const customerSale = sale(paymentMethod, customerId);
Read more about the await keyword

Crossdomain ServiceWorker load balancing

I'm trying to implement smth like crossdomain load balancing with ServiceWorker API.
My concept is:
After install on every request on fetch event I try to access main domain (https://example.com/)
If success I should return this to user with like event.respondWith(__response);
If failed (timed out or any other exception) I make CORS request to other server (https://balancer.com/) which returns other accessible domain (https://mirror1.example.com) and browser is redirected;
And I'm stucked on redirection step(((
So my current code is here
self.oninstall = function (event) {
event.waitUntil(self.skipWaiting());
};
self.onactivate = function (event) {
event.waitUntil(self.clients.claim());
};
self.initialUrl = false;
self.onfetch = async function (event) {
if (!self.initialUrl)
self.initialUrl = event.request.url;
if (self.initialUrl) {
event.respondWith(self.tryAccess(event))
} else {
event.respondWith(fetch(event.request));
}
};
self.tryAccess = async (event) => {
return new Promise(async (resolve, reject) => {
self.clients
.matchAll({type: 'window'})
.then(async (clients) => {
for (var i in clients) {
var _c = clients[0];
if (_c.url === event.request.url) {
try {
let __tryResponse = await fetch(event.request);
resolve(__tryResponse);
return;
} catch (e) {
let __json = await (await fetch("https://balancer.com/")).json();
return _c.navigate(__json.path).then(client => client.focus());
}
} else {
resolve();
}
}
});
});
};
Getting a reference to a WindowClient and forcibly changing its URL from inside of a fetch handler isn't the right way to redirect.
Instead, inside of your fetch handler, you can respond with a redirection response created by Response.redirect(). From the perspective of the browser, this will be treated just like any other redirection that might have originated from the server.
One thing to note is that if you initially request a subresource via a same-origin URL that results in a redirect to a cross-origin response, you might run into some issues. If your original requests are for cross-origin URLs and your potential redirects are also to cross-origin URLs, I think you'll be fine.
self.addEventListener('fetch', (event) => {
const fetchWithRedirection = async () => {
try {
// Use fetch(), caches.match(), etc. to get a response.
const response = await ...;
// Optional: also check response.ok, but that
// will always be false for opaque responses.
if (response) {
return response;
}
// If we don't have a valid response, trigger catch().
throw new Error('Unable to get a response.');
} catch (error) {
// Use whatever logic you need to get the redirection URL.
const redirectionURL = await ...;
if (redirectionURL) {
// HTTP 302 indicates a temporary redirect.
return Response.redirect(redirectionURL, 302);
}
// If we get to this point, redirection isn't possible,
// so just trigger a NetworkError.
throw error;
}
};
// You will probably want to wrap this in an if() to ensure
// that it's a request that you want to handle with redirection.
if (/* some routing criteria */) {
event.respondWith(fetchWithRedirection());
} else {
// Optionally use different response generation logic.
// Or just don't call event.respondWith(), and the
// browser will proceed without service worker involvement.
}
});

How to avoid sending multiple duplicate AJAX requests in axios

Is it possible to automatically throttle all requests going to a particular list of endpoints using axios? Perhaps using axios interceptor?
Currently I throttle the user action that sends the axios request, but the problem with that is that I have to write this everywhere I have a user action that results in some AJAX request. Like this
const throttledDismissNotification = throttle(dismissNotification, 1000)
const dismiss = (event: any) => {
throttledDismissNotification();
};
render() {
return (
<Button onClick={dismiss}>Dismiss Notification</Button>
)
}
This results in a lot of clutter and I was wondering if this could be automated.
Something like:
if(request.url in listOfEndpointsToThrottle && request.params in cacheOfPreviousRequestsToThisEndpoint) {
StopRequest();
}
Obviously this is pseudocode but you get the idea.
Perhaps you could try to use the Cancellation feature that axios provides.
With it, you can ensure that you don't have any two (or more, depending on your implementation) similar requests in a pending state.
Below, you will find a small simplified example of how to ensure that only the latest request is processed. You can adjust it a bit to make it function like a pool of requests
import axios, { CancelToken } from 'axios';
const pendingRequests = {};
const makeCancellable = (headers, requestId) => {
if (!requestId) {
return headers;
}
if (pendingRequests[requestId]) {
// cancel an existing request
pendingRequests[requestId].cancel();
}
const source = CancelToken.source();
const newHeaders = {
...headers,
cancelToken: source.token
};
pendingRequests[requestId] = source;
return newHeaders;
};
const request = ({
url,
method = 'GET',
headers,
id
}) => {
const requestConfig = {
url,
method,
headers: makeCancellable(headers || {}, id)
};
return axios.request(requestConfig)
.then((res) => {
delete pendingRequests[id];
return ({ data: res.data });
})
.catch((error) => {
delete pendingRequests[id];
if (axios.isCancel(error)) {
console.log(`A request to url ${url} was cancelled`); // cancelled
} else {
return handleReject(error);
}
});
};
export default request;
It's quite easy to throttle an axios request itself. The real headache is how to handle the promises that are returned from nullified requests. What is considered sane behavior when dealing with promises that are returned from a nullified axios request? Should they stay pending forever?
I don't see any perfect solution to this problem. But then I come to a solution that is kind of cheating:
What if we don't throttle the axios call, instead we throttle the actual XMLHttpRequest?
This makes things way easier, because it avoids the promise problem, and it's easier to implement. The idea is to implement a cache for recent requests, and if a new request matches a recent one, you just pull the result from cache and skip the XMLHttpRequest.
Because of the way axios interceptors work, the following snippet can be used to skip a certain XHR call conditionally:
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
/* check the cache, if hit, then intentionally throw
* this will cause the XHR call to be skipped
* but the error is still handled by response interceptor
* we can then recover from error to the cached response
**/
if (requestCache.isCached(config)) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.request = config
throw skipXHRError
} else {
/* if not cached yet
* check if request should be throttled
* then open up the cache to wait for a response
**/
if (requestCache.shouldThrottle(config)) {
requestCache.waitForResponse(config)
}
return config;
}
});
// This should be the *first* response interceptor to add
axios.interceptors.response.use(function (response) {
requestCache.setCachedResponse(response.config, response)
return response;
}, function (error) {
/* recover from error back to normality
* but this time we use an cached response result
**/
if (error.isSkipXHR) {
return requestCache.getCachedResponse(error.request)
}
return Promise.reject(error);
});
I have a similar problem, thru my research it seems to lack a good solution. All I saw were some ad hoc solutions so I open an issue for axios, hoping someone can answer my question https://github.com/axios/axios/issues/2118
I also find this article Throttling Axios requests but I did not try the solution he suggested.
And I have a discussion related to this My implementation of debounce axios request left the promise in pending state forever, is there a better way?
I finish one, #hackape thank you for you answer, the code is as follows:
const pendings = {}
const caches = {}
const cacheUtils = {
getUniqueUrl: function (config) {
// you can set the rule based on your own requirement
return config.url + '&' + config.method
},
isCached: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl] !== undefined
},
isPending: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
if (!pendings[uniqueUrl]) {
pendings[uniqueUrl] = [config]
return false
} else {
console.log(`cache url: ${uniqueUrl}`)
pendings[uniqueUrl].push(config)
return true
}
},
setCachedResponse: function (config, response) {
let uniqueUrl = this.getUniqueUrl(config)
caches[uniqueUrl] = response
if (pendings[uniqueUrl]) {
pendings[uniqueUrl].forEach(configItem => {
configItem.isFinished = true
})
}
},
getError: function(config) {
const skipXHRError = new Error('skip')
skipXHRError.isSkipXHR = true
skipXHRError.requestConfig = config
return skipXHRError
},
getCachedResponse: function (config) {
let uniqueUrl = this.getUniqueUrl(config)
return caches[uniqueUrl]
}
}
// This should be the *last* request interceptor to add
axios.interceptors.request.use(function (config) {
// to avoid careless bug, only the request that explicitly declares *canCache* parameter can use cache
if (config.canCache) {
if (cacheUtils.isCached(config)) {
let error = cacheUtils.getError(config)
throw error
}
if (cacheUtils.isPending(config)) {
return new Promise((resolve, reject) => {
let interval = setInterval(() => {
if(config.isFinished) {
clearInterval(interval)
let error = cacheUtils.getError(config)
reject(error)
}
}, 200)
});
} else {
// the head of cacheable requests queue, get the response by http request
return config
}
} else {
return config
}
});

JS promise passing between functions / wait for promises

I am trying to work through JS Promises in node.js and don't get the solution for passing promises between different function.
The task
For a main logic, I need to get a json object of items from a REST API. The API handling itself is located in a api.js file.
The request to the API inthere is made through the request-promise module. I have a private makeRequest function and public helper functions, like API.getItems().
The main logic in index.js needs to wait for the API function until it can be executed.
Questions
The promise passing kind of works, but I am not sure if this is more than a coincidence. Is it correct to return a Promise which returns the responses in makeRequest?
Do I really need all the promises to make the main logic work only after waiting for the items to be setup? Is there a simpler way?
I still need to figure out, how to best handle errors from a) the makeRequest and b) the getItems functions. What's the best practice with Promises therefor? Passing Error objects?
Here is the Code that I came up with right now:
// index.js
var API = require('./lib/api');
var items;
function mainLogic() {
if (items instanceof Error) {
console.log("No items present. Stopping main logic.");
return;
}
// ... do something with items
}
API.getItems().then(function (response) {
if (response) {
console.log(response);
items = response;
mainLogic();
}
}, function (err) {
console.log(err);
});
api.js
// ./lib/api.js
var request = require('request-promise');
// constructor
var API = function () {
var api = this;
api.endpoint = "https://api.example.com/v1";
//...
};
API.prototype.getItems = function () {
var api = this;
var endpoint = '/items';
return new Promise(function (resolve, reject) {
var request = makeRequest(api, endpoint).then(function (response) {
if (200 === response.statusCode) {
resolve(response.body.items);
}
}, function (err) {
reject(false);
});
});
};
function makeRequest(api, endpoint) {
var url = api.endpoint + endpoint;
var options = {
method: 'GET',
uri: url,
body: {},
headers: {},
simple: false,
resolveWithFullResponse: true,
json: true
};
return request(options)
.then(function (response) {
console.log(response.body);
return response;
})
.catch(function (err) {
return Error(err);
});
}
module.exports = new API();
Some more background:
At first I started to make API request with the request module, that works with callbacks. Since these were called async, the items never made it to the main logic and I used to handle it with Promises.
You are missing two things here:
That you can chain promises directly and
the way promise error handling works.
You can change the return statement in makeRequest() to:
return request(options);
Since makeRequest() returns a promise, you can reuse it in getItems() and you don't have to create a new promise explicitly. The .then() function already does this for you:
return makeRequest(api, endpoint)
.then(function (response) {
if (200 === response.statusCode) {
return response.body.items;
}
else {
// throw an exception or call Promise.reject() with a proper error
}
});
If the promise returned by makeRequest() was rejected and you don't handle rejection -- like in the above code --, the promise returned by .then() will also be rejected. You can compare the behaviour to exceptions. If you don't catch one, it bubbles up the callstack.
Finally, in index.js you should use getItems() like this:
API.getItems().then(function (response) {
// Here you are sure that everything worked. No additional checks required.
// Whatever you want to do with the response, do it here.
// Don't assign response to another variable outside of this scope.
// If processing the response is complex, rather pass it to another
// function directly.
}, function (err) {
// handle the error
});
I recommend this blog post to better understand the concept of promises:
https://blog.domenic.me/youre-missing-the-point-of-promises/

Categories