Performance hit for multiple API call using await keyword - javascript

There is a huge performance hit when I use await keyword for multiple API calls,
In the below example suppose persons array have 50 objects therefore it make 50 API calls (for..of loop) to get transaction details of a person (one api call) one at a time and do operation on it.
for (const person of persons) {
const trx = await transactionHistory(person.id); // method which trigger single API call (https endpoint)
// do some business operation on trx object
}
It takes around 7+ seconds to execute everything.
But when I tweak a little bit by making all the API call in foreach loop and assign it to a property and later use it, in await keyword. Then it takes roughly less than a second to execute everything.
persons.forEach(person => {
person.trxHistory = transactionHistory(person.id); // method which trigger single API call (https endpoint)
});
for (const person of persons) {
const trx = await person.trxHistory
// do some business operation on trx object
}
I am a little confused and unable to understand how it improved the performance with a big difference. Can anyone explain the difference between the two logic? I am a beginner level :)

In the second snippet, you are triggering the operations one after another asynchronously in the forEach before they are awaited. So it happens in the background. The result of the transactionHistory(person.id) is a Promise which is then assigned to the person.trxHistory.
Since the operations were already started in the background, when you ultimately come to the for-of loop to await on them they are already in-progress and you need to wait fewer time to get the result.
But in the earlier for-of loop in your first snippet, you were triggering the operations one by one:
Start the operation
Wait for it to finish
This was the sequence in your first example. Hence this was slow as it was sequential, whereas the latter one was concurrent.
persons.forEach(() => {
//operation already started in the background in an async way
person.trxHistory = transactionHistory(person.id); // method which trigger single API call (https endpoint)
});
for (const person of persons) {
const trx = await person.trxHistory
// do some business operation on trx object
}
So when you eventually come to the for-of loop, you are only awaiting on the last result synchronously.
You can trigger all the operations inside a Promise.all call and await those results one by one:
Promise.all(persons.map(p => transactionHistory(p.id)).then(async res => {
const trx = await res
// do some business operation on trx object
});
To make it clear take this mock API for instance:
This first snippet is the concurrent one, where all tasks are triggered before hand and then awaited so total time is the max time for each individual task (approx 7 secs):
const mockAPICall = (time) => {
return new Promise((res, rej) => {
console.log("Started execution");
setTimeout(() => res("Mocked return value"), time);
});
}
const waits = [{wait: 1000}, {wait: 2000}, {wait: 7000}]
waits.forEach(w => {
w.result = mockAPICall(w.wait);
});
const start = performance.now();
(async () => {
for (const w of waits){
let result = await w.result;
console.log(result);
}
console.log(performance.now() - start)
})();
This snippet is sequential, waits for each task to complete one-by-one so total time is the sum of all the waiting periods (approx 10 secs):
const mockAPICall = (time) => {
return new Promise((res, rej) => {
console.log("Started execution");
setTimeout(() => res("Mocked return value"), time);
});
}
const waits = [{wait: 1000}, {wait: 2000}, {wait: 7000}]
const start = performance.now();
(async () => {
for (const w of waits){
let result = await mockAPICall(w.wait);
console.log(result);
}
console.log(performance.now() - start);
})();

Related

Best way to to use async/promise/callbacks in for loop [duplicate]

This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 2 years ago.
I'm building a trading bot that needs to get stock names from separate files. But even I have used async function and await in my code, that doesn't work.
My index file init method.
const init = async () => {
const symbols = await getDownTrendingStock();
console.log("All stocks that are down: " + symbols);
const doOrder = async () => {
//do stuff
}
doOrder();
}
my getDownTrendeingStock file
const downStocks = []
function getDownTrendingStock () {
for(i = 0; i < data.USDTPairs.length; i++){
const USDTPair = data.USDTPairs[i] + "USDT";
binance.prevDay(USDTPair, (error, prevDay, symbol) => {
if(prevDay.priceChangePercent < -2){
downStocks.push(symbol)
}
});
}
return downStocks;
}
I have tried to also use async in for loop because the getDownTrendinStock function returns an empty array before for loop is finished. I didn't find the right way to do that because I was confused with all async, promise and callback stuff. What is the right statement to use in this situation?
Output:
All stocks that are down:
Wanted output:
All stocks that are down: [BTCUSDT, TRXUSDT, ATOMUSDT...]
I think the main issue in the code you posted is that you are mixing callbacks and promises.
This is what's happening in your getDownTrendingStock function:
You start iterating over the data.USDTPairs array, picking the first element
You call binance.prevDay. This does nothing yet, because its an asynchronous function that takes a bit of time. Notably, no data is added to downStocks yet.
You continue doing 1-2, still, no data is added
You return downStocks, which is still empty.
Your function is complete, you print the empty array
Now, at some point, the nodejs event loop continues and starts working on those asynchronous tasks you created earlier by calling binance.prevDay. Internally, it probably calls an API, which takes time; once that call is completed, it calls the function you provided, which pushes data to the downStocks array.
In summary, you didn't wait for the async code to complete. You can achieve this in multiple ways.
One is to wrap this in a promise and then await that promise:
const result= await new Promise((resolve, reject) => {
binance.prevDay(USDTPair, (error, prevDay, symbol) => {
if (error) {
reject(error);
} else {
resolve({prevDay, symbol});
}
});
});
if(result.prevDay.priceChangePercent < -2){
downStocks.push(result.symbol)
}
Note that you can probably also use promisify for this. Also, this means that you will wait for one request to finish before starting the next, which may slow down your code considerably, depending on how many calls you need; you may want to look into Promise.all as well.
Generally speaking, I use two technics:
const asyncFunc = () => {smthAsync};
const arrayToProcess = [];
// 1
const result = await arrayToProcess.reduce((acc, value) => acc.then(() => asyncFunc(value)), Promise.resolve(someInitialValue));
// 2
// here will be eslint errors
for(let i = 0 i < arrayToProcess.length; i+=1) {
const processResult = await asyncFunc(value);
// do with processResult what you want
};

Is this type of promise nesting good practice?

I just wanted to know if it is considered good practice to nest promises like in this example, or is there better alternatives ?
getDatabaseModel.searchId(idNoms).then(function([noms, elements]) {
getDatabaseModel.getLocalisations().then(function(localisations){
getDatabaseModel.getStates().then(function(states){
//Some code
})
})
})
Obviously, your promises are independent. So you should use Promise.all() to make it run parallel with the highest performance.
The Promise.all() method takes an iterable of promises as an input,
and returns a single Promise that resolves to an array of the results
of the input promises
var searchById = getDatabaseModel.searchId(idNoms);
var getLocalisations = getDatabaseModel.getLocalisations();
var getStates = getDatabaseModel.getStates();
var result = Promise.all([searchById, getLocalisations, getStates]);
result.then((values) => {
console.log(values);
});
For example, Let's say each promise takes 1s - So it should be 3s in total, But with Promise.all, actually it just takes 1s in total.
var tick = Date.now();
const log = (v) => console.log(`${v} \n Elapsed: ${Date.now() - tick}`);
log("Staring... ");
var fetchData = (name, ms) => new Promise(resolve => setTimeout(() => resolve(name), ms));
var result = Promise.all(
[
fetchData("searchById", 1000),
fetchData("getLocalisations", 1000),
fetchData("getStates", 1000)
]);
result.then((values) => {
log("Complete...");
console.log(values);
});
Besides, If you're concern about asyn/await with more elegant/concise/read it like sync code, then await keyword much says the code should wait until the async request is finished and then afterward it'll execute the next thing. While those promises are independent. So promise.all is better than in your case.
var tick = Date.now();
const log = (v) => console.log(`${v} \n Elapsed: ${Date.now() - tick}`);
var fetchData = (name, ms) => new Promise(resolve => setTimeout(() => resolve(name), ms));
Run();
async function Run(){
log("Staring... ");
var a = await fetchData("searchById", 1000);
var b = await fetchData("getLocalisations", 1000);
var c = await fetchData("getStates", 1000);
log("Complete...");
console.log([a, b, c]);
}
Promises were made to avoid callback hell, but they are not too good at it too. People like promises until they find async/await. The exact same code can be re-written in async/await as
async getModel(idNoms){
const [noms, elements] = await getDatabaseModel.searchId(idNoms);
const localisations = await getDatabaseModel.getLocalisations();
const state = await getDatabaseModel.getStates():
// do something using localisations & state, it'll work
}
getModel(idNoms);
Learn async/await here
IMO it's a little hard to read and understand. Compare with this:
getDatabaseModel.searchId(idNoms)
.then(([noms, elements]) => getDatabaseModel.getLocalisations())
.then(localization => getDatabaseModel.getStates());
As #deceze pointed out there are two things to note:
These functions are called serially
They don't seem to depend on each other as the noms, elements and localization are not used at all.
With Promise.all you can mix and match however you want:
// Call `searchId` and `getState` at the same time
// Call `getLocalisations` after `searchId` is done
// wait for all to finish
Promise.all([
getDatabaseModel.searchId(idNoms).then(([noms, elements]) => getDatabaseModel.getLocalisations()),
getDatabaseModel.getStates()
]).then(([result1, result2]) => console.log('done'));
// Call all 3 at the same time
// wait for all to finish
Promise.all([
getDatabaseModel.searchId(idNoms),
getDatabaseModel.getLocalisations(),
getDatabaseModel.getStates(),
]).then(([result1, result2, result3]) => console.log('done'));

Node.js - Await for all the promises thrown inside a loop

I'm dealing with a loop in Node.js that performs two tasks for every iteration of a loop. To simplify, the code is summarized in:
Extract products metadata from a web page (blocking task).
Save all the products metadata to a database (asynchronous task).
The save operation (2) will perform about 800 operations in a database, and it doesn't need to block the main thread (I can still extracting products metadata from the web pages).
So, that being said, awaiting for the products to being saved doesn't have any sense. But if I throw the promises without awaiting for them, in the last iteration of the loop the Node.js process exits and all the pending operations are not finished.
Which is the best approach to solve this? Is it possible to achieve it without having a counter for finished promises or emitters? Thanks.
for (let shop of shops) {
// 1
const products = await extractProductsMetadata(shop);
// 2
await saveProductsMetadata(products);
}
Collect the promises in an array, then use Promise.all on it:
const storePromises = [];
for (let shop of shops) {
const products = await extractProductsMetadata(shop); //(1)
storePromises.push(saveProductsMetadata(products)); //(2)
}
await Promise.all(storePromises);
// ... all done (3)
Through that (1) will run one after each other, (2) will run in parallel, and (3) will run afterwards.
For sure you can also run (1) and (2) in parallel:
await Promise.all(shops.map(async shop => {
const products = await extractProductsMetadata(shop); //(1)
await saveProductsMetadata(products);
}));
And if an error occured in one of the promises, you can handle that with a try / catch block, to make sure all other shops won't be affected:
await Promise.all(shops.map(async shop => {
try {
const products = await extractProductsMetadata(shop); //(1)
await saveProductsMetadata(products);
} catch(error) {
// handle it here
}
}));
how to signal node to finish the process ?
You could manually call process.exit(0);, but that hides the real problem: NodeJS exits automatically if there is no listener attached anymore. That means that you should close all database connections / servers / etc. after the code above is done.
We are creating packs of data to treat. When we treat the data, we do all the get synchronously, and all the save asynchronously.
I have not handled the failure part, I let you add it to it. appropriate try/catch or function encapsulation will do it.
/**
* Call the given functions that returns promises in a queue
* options = context/args
*/
function promiseQueue(promisesFuncs, options = {}, _i = 0, _ret = []) {
return new Promise((resolve, reject) => {
if (_i >= promisesFuncs.length) {
return resolve(_ret);
}
// Call one
(promisesFuncs[_i]).apply(options.context || this, options.args || [])
.then((ret: any) => promiseQueue(promisesFuncs, _i + 1, options, [
..._ret,
ret,
]))
.then(resolve)
.catch(reject);
});
}
function async executePromiseAsPacks(arr, packSize, _i = 0) {
const toExecute = arr.slice(_i * packSize, packSize);
// Leave if we did execute all packs
if (toExecute.length === 0) return true;
// First we get all the data synchronously
const products = await promiseQueue(toExecute.map(x => () => extractProductsMetadata(x)));
// Then save the products asynchronously
// We do not put await here so it's truly asynchronous
Promise.all(toExecute.map((x, xi) => saveProductsMetadata(products[xi])));
// Call next
return executePromiseAsPacks(arr, packSize, _i + 1);
}
// Makes pack of data to treat (we extract synchronously and save asynchronously)
// Made to handle huge dataset
await executePromisesAsPacks(shops, 50);

Understanding Where to Place `await` Keyword

Just to be sure I understand how async/await works, I'd like to confirm something. Let me first create a couple of functions:
let resolveAfter2Seconds = () => {
console.log("starting slow promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(20);
console.log("slow promise is done");
}, 2000);
});
};
let resolveAfter1Second = () => {
console.log("starting fast promise");
return new Promise(resolve => {
setTimeout(function() {
resolve(10);
console.log("fast promise is done");
}, 1000);
});
};
Now, take these two blocks of code for example:
let concurrentStart = async () => {
console.log('==CONCURRENT START with await==');
const slow = resolveAfter2Seconds();
const fast = resolveAfter1Second();
console.log(await slow);
console.log(await fast);
}
Now, is it the case that the above is functionally equivalent to this:
let concurrentStart = async () => {
console.log('==CONCURRENT START with await==');
const slow = await resolveAfter2Seconds();
const fast = await resolveAfter1Second();
console.log(slow);
console.log(fast);
}
In other words, I can either put the await keyword right before the function call await resolveAfter25Seconds(), or... I can put the await in the console log that triggers the firing of that function - console.log(await slow);.
Will the result be the same, in the sense that async/await will work in the same way in both cases -- in which case you could accomplish the same thing with either approach?
They behave differently; your first example runs for two seconds and your second example runs for three seconds.
In the first case, slow and fast are allowed to start in parallel; in the second case, you force the first promise to resolve before even starting the second one.
A real-world use-case of kicking off many promises without awaiting them is if you wanted to fetch multiple URLs in parallel.
// assuming getUrlContents() returns a Promise
let promises = [
getUrlContents('http://url1.com/'),
getUrlContents('http://url2.com/'),
getUrlContents('http://url3.com/')
];
// all URLs are currently fetching
// now we wait for all of them; doesn't particularly matter what order
// they resolve
let results = [
await promises[0],
await promises[1],
await promises[2]
];
Another variation of this would run much more slowly (and usually not what you want unless you really DID need the contents of one before being able to proceed with the next one):
let a = await getUrlContents('http://url1.com/')
let b = await getUrlContents('http://url2.com/')
let c = await getUrlContents('http://url3.com/')
let results = [ a, b, c ];
The download for b wouldn't start until after a completed, and so on.

await loop vs Promise.all [duplicate]

This question already has answers here:
Any difference between await Promise.all() and multiple await?
(6 answers)
Closed 4 years ago.
Having a set of async operations on db to do, I'm wondering what's the difference performance-wise of doing a "blocking" await loop versus a Promise.all.
let insert = (id,value) => {
return new Promise(function (resolve, reject) {
connnection.query(`insert into items (id,value) VALUES (${id},"${value}")`, function (err, result) {
if (err) return reject(err)
return resolve(result);
});
});
};
Promise.all solution (it needs a for loop to builds the array of promises..)
let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i,"..string.."))
Promise.all(inserts).then(values => {
console.log("promise all ends");
});
await loop solution
let inserts = [];
(async function loop() {
for (let i = 0; i < SIZE; i++) {
await insert(i, "..string..")
}
console.log("await loop ends");
})
Edit: thanks for the anwsers, but I would dig into this a little more.
await is not really blocking, we all know that, it's blocking in its own code block. An await loop sequentially fire requests, so if in the middle 1 requests takes longer, the other ones waits for it.
Well this is similar to Promise.all: if a 1 req takes longer, the callback is not executed until ALL the responses are returned.
Your example of using Promise.all will create all promises first before waiting for them to resolve. This means that your requests will fire concurrently and the callback given to Promise.all(...).then(thisCallback) will only fire if all requests were successful.
Note: promise returned from Promise.all will reject as soon as one of the promises in the given array rejects.
const SIZE = 5;
const insert = i => new Promise(resolve => {
console.log(`started inserting ${i}`);
setTimeout(() => {
console.log(`inserted ${i}`);
resolve();
}, 300);
});
// your code
let inserts = [];
for (let i = 0; i < SIZE; i++) inserts.push(insert(i, "..string.."))
Promise.all(inserts).then(values => {
console.log("promise all ends");
});
// requests are made concurrently
// output
// started inserting 0
// started inserting 1
// started inserting 2
// ...
// started inserting 4
// inserted 0
// inserted 1
// ...
// promise all ends
Note: It might be cleaner to use .map instead of a loop for this scenario:
Promise.all(
Array.from(Array(SIZE)).map((_, i) => insert(i,"..string.."))
).then(values => {
console.log("promise all ends");
});
Your example of using await on the other hand, waits for each promise to resolve before continuing and firing of the next one:
const SIZE = 5;
const insert = i => new Promise(resolve => {
console.log(`started inserting ${i}`);
setTimeout(() => {
console.log(`inserted ${i}`);
resolve();
}, 300);
});
let inserts = [];
(async function loop() {
for (let i = 0; i < SIZE; i++) {
await insert(i, "..string..")
}
console.log("await loop ends");
})()
// no request is made until the previous one is finished
// output
// started inserting 0
// inserted 0
// started inserting 1
// ...
// started inserting 4
// inserted 4
// await loop ends
The implications for performance in the above cases are directly correlated to their different behavior.
If "efficient" for your use case means to finish up the requests as soon as possible, then the first example wins because the requests will be happening around the same time, independently, whereas in the second example they will happen in a serial fashion.
In terms of complexity, the time complexity for your first example is equal to O(longestRequestTime) because the requests will happen essentially in parallel and thus the request taking the longest will drive the worst-case scenario.
On the other hand, the await example has O(sumOfAllRequestTimes) because no matter how long individual requests take, each one has to wait for the previous one to finish and thus the total time will always include all of them.
To put things in numbers, ignoring all other potential delays due to the environment and application in which the code is ran, for 1000 requests, each taking 1s, the Promise.all example would still take ~1s while the await example would take ~1000s.
Maybe a picture would help:
Note: Promise.all won't actually run the requests exactly in parallel and the performance in general will greatly depend on the exact environment in which the code is running and the state of it (for instance the event loop) but this is a good approximation.
The major difference between the two approaches is that
The await version issues server requests sequentially in the loop. If one of them errors without being caught, no more requests are issued. If request errors are trapped using try/catch blocks, you can identify which request failed and perhaps code in some some form of recovery or even retry the operation.
The Promise.all version will make server requests in or near parallel fashion, limited by browser restrictions on the maximum number of concurrent requests permitted. If one of the requests fails the Promise.all returned promise fails immediately. If any requests were successful and returned data, you lose the data returned. In addition if any request fails, no outstanding requests are cancelled - they were initiated in user code (the insert function) when creating the array of promises.
As mentioned in another answer, await is non blocking and returns to the event loop until its operand promise is settled. Both the Promise.all and await while looping versions allow responding to other events while requests are in progress.
Each has different advantages, it's up to us which one we need to solve our problem.
await loop
for(let i = 0;i < SIZE; i++){
await promiseCall();
}
It will call all promises in parallel if any promise rejected it won't have any effect on other promises.
In ES2018 it has simplified for certain situation like if you want to call the second iteration only if the first iteration got finished, refer the following ex.
async function printFiles () {
const files = await getFilePaths()
for await (const file of fs.readFile(file, 'utf8')) {
console.log(contents)
}
}
Promise.all()
var p1 = Promise.resolve(32);
var p2 = 123;
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [32, 123, "foo"]
});
This will execute every promise sequentially and finally return combined revolved values array.
If any one of these promise get rejected it will return value of that rejected promise only. follow following ex,
var p1 = Promise.resolve(32);
var p2 = Promise.resolve(123);
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 100);
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // 123
});

Categories

Resources