How to make a simultaneous call to api? - javascript

I have an async function that calls my API simultaneously, but Promise.all() resolving after about 2 seconds for me (One request resolving in about 200ms). That means my requests resolving one after another, not simultaneously even if they started asyncronious.
// don't run in client side of browser - CORS
async asyncData () {
axios.interceptors.request.use(config => {
console.log('Start: ', new Date)
return config
})
const promise1 = axios.get('https://jsonplaceholder.typicode.com/comments')
const promise2 = axios.get('https://jsonplaceholder.typicode.com/posts')
const promise3 = axios.get('https://jsonplaceholder.typicode.com/todos')
const promise4 = axios.get('https://jsonplaceholder.typicode.com/users')
const promise5 = axios.get('https://jsonplaceholder.typicode.com/photos')
const promise6 = axios.get('https://jsonplaceholder.typicode.com/photos')
const promise7 = axios.get('https://jsonplaceholder.typicode.com/photos')
const promise8 = axios.get('https://jsonplaceholder.typicode.com/photos')
const promise9 = axios.get('https://jsonplaceholder.typicode.com/photos')
await Promise.all([
promise1,
promise2,
promise3,
promise4,
promise5,
promise6,
promise7,
promise8,
promise9,
]).then(data => {
console.log('End: ', new Date)
})
}
I am confused because this code below resolving in 4000ms, that means they starting simultaneously and resolving after ending of biggest timeout - 4000. This time Promises not resolving in chain
async asyncData () {
function startTimer (time) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, time)
})
}
console.time('Timer')
Promise.all([
startTimer(1000),
startTimer(4000),
startTimer(2000),
]).then(function () {
console.timeEnd('Timer')
})
}
P.S. Sorry for bad English ¯\_(ツ)_/¯

Your code is fine. The problem is that large numbers of simultaneous network requests, especially those made to the same endpoint, often take longer to resolve than if only one request was made - even if you made all those requests in parallel. Here's a screenshot from Chrome devtools of the requests:
As you can see, all the requests are being fired off immediately, but the subsequent ones are taking longer to resolve. Often this is caused by the endpoint (here, jsonplaceholder) throttling requests.
If you were making requests to your own server, and both your server and the script source had sufficient bandwidth, connectivity, and no throttling, the parallel requests would resolve at least close to the same time.
(Browsers may restrict the total number of connections allowed at any one time, as may OSs)

Related

Question: Concurrent queue with max concurrency (per cacheKey)

this is more of a opinionated question. I do have a working solution, but I'm not 100% comfortable with it, as it has it's flaws.
Maybe someone can help me to improve this.
Goal:
I have an external api that only allows 4 calls to be made concurrently against it (for each user). Our app can impersonate multiple users at once.
So the issue comes, if more than 4 calls are made against the api simultaneously. (sometimes more than 20 calls are made)
Using a batched approach with Promise.all and chunking would be very inefficient, as the calls have a different runtime each.
Ideally, the queue would work FIFO and as soon as one call finishes, the next call is started. At the current standing, I created 4 own FIFO queues with somewhat of a Promise chain and I fill these evenly (if all are running).
The problem that I have is, that I do not know how long a request is running.
So choosing one of the queues can lead to an overall longer runtime as necessary.
The calls are automatically rejected after 30s from the external api, so no dead lock here.
Another problem that I have is, that I have to return the data provided from the api to a dependency. The queue is called/filled from withan a callback function...
This is wrapped in a Promise itself, so we can wait as long as we want.
But storing the values in a cache for later retrieval is no option either.
So long story short, here is the code
class QueuingService {
/** ~FIFO queue */
private static queue: Record<string, { promise: Promise<Function>, uuid: string }[]> = {};
private static MAX_CONCURRENCY = 4
/** cacheKey is used as cachekey for grouping in the queue */
public static async addToQueueAndExecute(func: Function, cacheKey: string) {
let resolver: Function | null = null;
let promise = new Promise<Function>(resolve => {
resolver = resolve;
});
//in the real world, this is a real key created by nanoId
let uuid = `${Math.random()}`;
Array.isArray(this.queue[cacheKey])
? this.queue[cacheKey].push({ promise: promise, uuid: uuid })
: this.queue[cacheKey] = [{ promise: promise, uuid: uuid }];
//queue in all calls, until MAX_CONCURRENCY is reached. After that, slice the first entry and await the promise
if (this.queue[cacheKey].length > this.MAX_CONCURRENCY) {
let queuePromise = this.queue[cacheKey].shift();
if (queuePromise){
await queuePromise.promise;
}
}
//console.log("elements in queue:", this.queue[cacheKey].length, cacheKey)
//technically this wrapping is not necessary, but it makes to code more readable imho
let result = async () => {
let res = await func();
if (resolver) {
//resolve and clean up
resolver();
this.queue[cacheKey] = this.queue[cacheKey].filter(elem => elem.uuid !== uuid);
}
//console.log(res, cacheKey, "finshed after", new Date().getTime() - enteredAt.getTime(),"ms", "entered at:", enteredAt)
return res;
}
return await result();
}
}
async function sleep(ms:number){
return await new Promise(resolve=>window.setTimeout(resolve,ms))
}
async function caller(){
/* //just for testing with fewer entries
for(let i=0;i<3;i++){
let groupKey = Math.random() <0.5? "foo":"foo"
QueuingService.addToQueueAndExecute(async ()=>{
await sleep(Math.floor(4000-Math.random()*2000));
console.log(i, new Date().getSeconds(), new Date().getMilliseconds(),groupKey)
return Math.random()
},groupKey)
}
*/
for(let i=0;i<20;i++){
let groupKey = Math.random() <0.5? "foo":"foo";
let startedAt = new Date();
QueuingService.addToQueueAndExecute(async ()=>{
await sleep(Math.floor(4000-Math.random()*2000));
console.log(i, new Date().getTime()-startedAt.getTime(),groupKey)
return Math.random()
},groupKey)
}
}
caller()
Also, here is a playground with the code to play around with:
https://www.typescriptlang.org/play?#code/MYGwhgzhAECKCuBTeBLAdgcwMqIE4DcVhFoBvAWACgBIAegCp7oA-AMQElWB5aARySTR6tKtQAOuFPjAAXEhBmyifAYgBc0AEqJgAe1wATADwLJmADRloE3QFsUEddAAKuOw8RHW8NMBkpdNAA+S3hUAw1TdAxoAF8AbQBdIOgAXjJYgG5RCSlZeUV-YGgAWQBBAA0AfQBhLgA5GoBVTU0AUUaATTToABYqUQYmYDBgAAtEAGlEAE9oB2h4RwNoSGgR8cQAa1noADN9aAw3eDFo+bRoGQmVZBJhHPgAIxBlBSViyBnfVYMDABVdAg7mU0AY2gAPHTwOQACj2PmAGm8vn8gUsGwm0xmkRkZgwAEoyKJqCBEDJoLhEBBdCB8HhkYi0ZcAD7QNDwEAgHocrnZGik8nWNz2Rw8xAAdxcIo8XiZAWCsKpNLpJFSKQoAuoytp9NwPR1qv51GosQJ-OglqtltotHQVxuVLA3Il+hABks1wWCzAlMQzugOzmwCdchWTzmaDAaF07AMJLJFLCKBW6QABgASUglWRjAB0uGjBjssIJsTT-IGArKuELMzzDhrddhXogef4d3imKms0SBJJ1AA-A6HO3VF3Rlje3mxEsxrDSML3I4NDZRYhQuENMmVmaBxpW2PO93sYkevFF2uPKuZY5Nynt+E4olKwLbR3BPbndyRlyIKE0H8blymqOpGhadounmGAnU2Aw82gMo9jkfVrlkSwIFeYgHRIPYUFwBRoEQQDcDmItVglMAUApa4SCvRwSRQPZoBbMZRw-RAJ02U88zJTBrmgFJDxA2oGmaVoOhqToiU1E1BQpDjXGXNURzbDiuKnGZEjzCA2OQ0tjRNJiWMU29EAJWS5LASjqNuJAlPXGczIta1XMtWISQ8yg3JtWg9DQFVEF43QMFhAAiRAyVsYiZBge0OLUMLPTYtTxxPac+Iwa4MUnHsZn7SgSVtORxjQIhvzmVtoAlQsxDOTBoPZXQKTQHRqQgMBSMsJ4YXmClbDAHYYBkXR1l0AwSFsfQSCdAwwBeEgUFsMZdATIVlU5Cl0i+H5SzSDUB0TP0YG2myKQRXwDIHYylWpXU8Bkgc6FoQ16VWMF1jJaNFjEJ7XrwK6tWoQ91PSrSehBtLcp4vCQBQ2FIsQWx9qIqK8x3aAAEJUnSHdzQHLyfNc21-MC4LQuVHLuNmSwwrwgKJhWMBkLwJL2UlaAABF8lLPMMHJf4lsQPaAFoiMAvBEAMMoZD5gWhdLcwwtsCA2YiiWqSZmREssGLJelmQCoHKkZHgXBLmVQyvJJE2zcuayqIpDa4cB00qGtygduKC6-AVaBMMQRAxFhFW1A5Wwnge2TbfNijHfZqUHI8W6VXpdUJXQYsJR0+Xot0GEU-u8wVYJAqPa9-Z5UCdZvwBiyqGtBhoFtAArJZzsOOQFHODOBL2SU8HFvEUGpBurQOXBYSOlBUgABkyFAjAAZgXgBqVf6+8nyjuOfOxGxHoc2uAsixLIkjFnvMAFZhzp3RdDCxKDgfse3OBVBMBwAgiCCsA-kBd+iBQTgihMAGEwsK6lnVJqIm1oHa2QDkHWER98x7BAPfSevRZ7YJFigk+YIz70AAEzYNnqXFysCxoBVpEFdBoUUCWFalKbmcICRyxkDgfyBgICKwTlzHmbD+YyBKCgLkHguE8IJOYXepxsQFUoZaGOlw8GFgIbYUsr9XKxGkScfesx5FWkJlaB4W9LSaInlPIUM956LxIWvDeMDt5ChkXouY6QVGn3UefS+N9oB3wfk-e+YUKGuSOu8XAYYZbimYQIkJ1p37RC-oQYgeY-4AiBKoYBkJoRwkgQSaBmiibwIpIg4OeC0EYNhFgnBHi1GlmIaQ8hhSfKkxoeTWEDC+EsOFoI3OPSRbhMibLIRgtoqKxcXI5pbklGlFzPg4sXiplxB0XvSZpi4iaPdlQX8ZJJ4EiAA

sinon useFakeTimers not resolving last promise-timeout, test times out

I want to test my logic and ensure that I don't exceed a limit of X requests every X seconds. Using mocha#^9.1.3, chai#^4.3.4 and sinon#^12.0.1, latest versions as of this comment.
I've read answers to very similar problems to mine, tried applying the knowledge gained but to no avail:
https://stackoverflow.com/a/52196951 — great explanation as to the why, however I must've misunderstood something
https://stackoverflow.com/a/55448441 — similar to 1.
https://stackoverflow.com/a/43048888, https://stackoverflow.com/a/60629795 — adding clock.tickAsync inside the wait utility function would not let me step through the code
I have a wait utility function looking like this:
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
and my code to reproduce my issue in my test file looks like (please do read comments in code):
import sinon from 'sinon';
import got from 'got';
describe('sinon usefaketimers promise-timeouts', () => {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
// i've also tried this
// clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
it('steps through all promise-timeouts manually', async () => {
const main = async () => {
console.log('starting');
await wait(1000);
console.log('waited 1s');
await wait(1000);
console.log('waited 2s');
// switching out `got()` below to `await Promise.resolve()`
// finishes the test successfully
await got('https://jsonplaceholder.typicode.com/todos/1').json(); // <-- switch to `await Promise.resolve()` makes it work
console.log('request done');
await wait(1000);
console.log('waited 3s');
};
const promise = main();
await clock.tickAsync(1000); // I have tried adding different combinations
await clock.tickAsync(1000); // of `await Promise.resolve()` after the tick(s)
await clock.tickAsync(1000);
return promise;
});
});
and the resulting output is, (it hangs after "request done", missing the last line "waited 3s"):
starting
waited 1s
waited 2s
request done
Note, I've tried switching out got to superagent and node-fetch without any success. I'm not entirely sure why they would be any different from Promise.resolve as they are all promises returned and awaited on.
Really hope to clear up whatever I didn't get from reading above questions (and so many other threads) from yesterday and todays debugging.

How to post multiple Axios requests at the same time?

At this moment I have a webpage in which a long list of Axios POST calls are being made. Now, the requests seem to be sent in parallel (JavaScript continues sending the next request before the result is received).
However, the results seem to be returned one by one, not simultaneously. Let's say one POST call to the PHP script takes 4 seconds and I need to make 10 calls. It would currently take 4 seconds per call, which would be 40 seconds in total. I hope to find a solution to both and receive all results at approximately the same time (~4 seconds) instead of ~40 seconds.
Now I've read about threads, multithreading in NodeJS using Workers. I've read that JavaScript itself is only single-threaded, so it may not allow this by itself.
But I'm not sure where to go from here. All I have are some ideas. I'm not sure whether or not I'm heading into the right direction and if I am, I am not sure how to use Workers in NodeJS and apply it in my code. Which road should I take? Any guidance would be highly appreciated!
Here is a small piece of example code:
for( var i = 0; i < 10; i++ )
{
window.axios.post(`/my-url`, {
myVar: 'myValue'
})
.then((response) => {
// Takes 4 seconds, 4 more seconds, 4 more seconds, etc
// Ideally: Takes 4 seconds, returns in the same ~4 seconds, returns in the same ~4 seconds, etc
console.log( 'Succeeded!' );
})
.catch((error) => {
console.log( 'Error' );
});
// Takes < 1 second, < 1 more second, < 1 more second, etc
console.log( 'Request sent!' );
}
There are three cases via you can achieve your goal.
For simultaneous requests with Axios, you can use Axios.all()
axios.all([
axios.post(`/my-url`, {
myVar: 'myValue'
}),
axios.post(`/my-url2`, {
myVar: 'myValue'
})
])
.then(axios.spread((data1, data2) => {
// output of req.
console.log('data1', data1, 'data2', data2)
}));
you can use Promise.allSettled(). The Promise.allSettled() method returns a promise that resolves after all of the given promises have either resolved or rejected,
You can try to use Promise.all() but it has the drawback that if any 1 req failed then it will fail for all and give o/p as an error(or in catch block)
but the best case is the first one.
For simultaneous requests with Axios you can use Axios.all().
axios.all([
axios.get('https://api.github.com/users/MaksymRudnyi'),
axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
// Both requests are now complete
console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));
Also, you can use Promise.all(). Works similar:
Promise.all([
fetch('https://api.github.com/users/MaksymRudnyi'),
fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
console.log(error);
});
But, with Promise.all() there is a specific behavior. In case at least one request will be rejected - the all request will be rejected and code will go to .catch() sections. It's OK in case you need to be sure that all requests are resolved.
In case when it's OK when some of your requests are rejected consider using Promise.allSettled(). The Promise.allSettled() method returns a promise that resolves after all of the given promises have either resolved or rejected, with an array of objects that each describes the outcome of each promise.
Try in this way
window.axios.all([requestOne, requestTwo, requestThree])
.then(axios.spread((...responses) => {
const responseOne = responses[0]
const responseTwo = responses[1]
const responesThree = responses[2]
// use/access the results
})).catch(errors => {
// react on errors.
})
If you want to have it within a loop, you can have slightly modified version of #deelink as below
let promises = [];
for (i = 0; i < 10; i++) {
promises.push(
window.axios.post(`/my-url`, {
myVar: 'myValue'})
.then(response => {
// do something with response
})
)
}
Promise.all(promises).then(() => console.log('all done'));
Try this with Axios.all and
Use Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled Promise MDN ref Link
import axios from 'axios';
let one = "https://api1"
let two = "https://api2"
let three = "https://api3"
const requestOne = axios.get(one);
const requestTwo = axios.get(two);
const requestThree = axios.get(three);
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((...responses) => {
const responseOne = responses[0]
const responseTwo = responses[1]
const responesThree = responses[2]
// use/access the results
console.log("responseOne",responseOne);
console.log("responseTwo",responseTwo);
console.log("responesThree",responesThree);
})).catch(errors => {
console.log(errors);
})
Ref Link
Find full example here for axios
You can use this way
const token_config = {
headers: {
'Authorization': `Bearer ${process.env.JWD_TOKEN}`
}
}
const [ res1, res2 ] = await Axios.all([
Axios.get(`https://api-1`, token_config),
Axios.get(`https://api-2`, token_config)
]);
res.json({
info: {
"res_1": res1,
"res_2": res2
}
});
This is weird and shouldn't happen. The Javascript engines are single threaded but the Web APIs (which are internally used when making AJAX requests) are not. So the requests should be made approximately at the same time and the response times should depend on server processing times and network delays.
Web browsers have a limit on the number of connections per server (6 in chrome https://bugs.chromium.org/p/chromium/issues/detail?id=12066) which would explain some serialization. But not this.
Since the requests takes 4 seconds, which is long, my guess is that the server is the problem. It may only be able to handle 1 connection at a time. Do you have control over it?

Create an order for JSON to be loaded

I have multiple external JSON files that need to be loaded on my page. Is there a way to set an order for them to be loaded.
JavaScript
const func1 = () => {
$.getJSON(json1, result => {});
$.getJSON(json2, result => {});
}
...
const func2 = () => {
$.getJSON(json3, result => {});
$.getJSON(json4, result => {});
}
Right now I have 4 different getJSON and now as I can see, this is the loading order = json1=>json3=>json4=>json2.
Is there a way to set an order on when to load the json files. I want to load it json1=>json2=>json3=>json4?
The issue you're seeing is because the requests are all asynchronous. As such you are entirely at the mercy of the network between the client's machine and the receiving server, as well as the load the server is under at that given moment and how strenuous each task you're asking to be done is, as to which of those requests are received and responded to first.
There are ways to mitigate this in your client-side logic, such as request chaining or synchronous requests, however the former has a negative effect of UI performance and the latter is terrible practice as it locks the browser until all requests complete.
The best approach to take if you need to rely on the order of the response, is to instead aggregate all of these requests in to a single one and send that alone. Your client-side logic can then continue processing as it has access to all the data you require. This will also scale much better in terms of server performance. It may require some changes to your server side logic, however, but this is a small price for both better client-side logic and server-side performance.
You can queue them. Initiate new api call once previous has succeeded.
const jsonData = [];
$.getJSON(json1, result => {
jsonData.push(result);
$.getJSON(json2, result => {
jsonData.push(result);
$.getJSON(json3, result => {
jsonData.push(result);
$.getJSON(json4, result => {
jsonData.push(result);
processJsonData(jsonData);
});
})
})
})
function processJsonData(jsonData) {
const [
json1,
json2,
json3,
json4
] = jsonData;
// Further proccessing
}
While theoretically possible, it is not advised.
I would rather make one request out of those 4 and handle that one request, or send all 4 asynchronously and continue work, when all 4 resolved.
Here would be the simple solution to send them one after another:
$.getJSON(json1, result => {
$.getJSON(json2, result => {
$.getJSON(json3, result => {
$.getJSON(json4, result => {});
});
});
});
Here would be a solution, to write it with promises and async/await (Its a bit cleaner):
function sendJsonRequest(jsonData) {
return new Promise(resolve => {
$.getJSON(jsonData, result => resolve(result));
});
}
async function getData() {
let data1 = await sendJsonRequest(json1);
let data2 = await sendJsonRequest(json2);
let data3 = await sendJsonRequest(json3);
let data4 = await sendJsonRequest(json4);
}
All of your requests are done simultaneously, thus, the order you observe is related to the reponse time of your backend.
If you absolutely want to wait for a request before the next one starts, then just chain them like that:
$.getJSON(json1, result => {
$.getJSON(json2, result => {
$.getJSON(json3, result => {
$.getJSON(json4, result => {})
})
})
})
And if you want to avoid indentation :
const func1 = async () => {
const result1 = await $.getJSON(json1);
const result2 = await $.getJSON(json2);
const result3 = await $.getJSON(json3);
//...
}

Multiple fetch() with one signal in order to abort them all

I saw this other answer: https://stackoverflow.com/a/47250621/2809729
So can I abort multiple fetch request using just one signal?
At the time I was writing the question, I already found the solution in this post on how to abort fetches from one of the pioneers that were working to the implementation of an abort.
So yes you can :)
Here a brief example:
async function fetchStory({ signal }={}) {
const storyResponse = await fetch('/story.json', { signal });
const data = await storyResponse.json();
const chapterFetches = data.chapterUrls.map(async url => {
const response = await fetch(url, { signal });
return response.text();
});
return Promise.all(chapterFetches);
}
In the above example, the same signal is used for the initial fetch, and for the parallel chapter fetches. Here's how you'd use fetchStory:
const controller = new AbortController();
const signal = controller.signal;
fetchStory({ signal }).then(chapters => {
console.log(chapters);
});
In this case, calling controller.abort() will reject the promise of the in-progress fetches.
I abstracted the solution to this problem, and came up with "fetch-tx", fetch transaction, which provides a single abort function to all related fetch operations.
you can find it here:
fetch-tx

Categories