How to replace multiple async/await calls with Promise.all? - javascript

I have the following try/catch block which is making 3 different api calls.
The following code is working fine but it is taking lot of time to execute when firstData has large dataset.
try {
const firstData = await myservice1.myservice1Func();
for(let i=0; i<firstData.total; i++){
const hostName = firstData.rows[i]['hostname'];
if (hostName !== null && firstData.rows[i]['myservice1Id'] !== null) {
const aRes = await myService2(hostName);
firstData.rows[i]['mylist'] =
aRes[0].dataValues;
}
if (hostName !== null && firstData.rows[i]['type'].includes('type1')) {
const oRes = await myService3(hostName);
firstData.rows[i]['ores'] = oRes.rows[0];
}
if (hostName !== null && firstData.rows[i]['type'].includes('type2')) {
const vRes = await myService4(hostName);
firstData.rows[i]['vRes'] = vRes.rows[0];
}
}
return firstData;
} catch (err) {
console.log(err);
}
Here,
const firstData =
{
"total": 2,
"rows": [
{
"hostname": "abc.com",
"ipAddress": "11.11.11.11",
"myservice1Id": "ee0f77c9-ef15",
"type": "type1"
},
{
"hostname": "cde.com",
"ipAddress": "12.12.12.12",
"type": "type2",
"myservice1Id": null
}
]
}
const aRes =
[
{
"listType": "list1",
"createdAt": "2020-12-07"
}
]
const oRes =
{
"rows": [
{
"status": "FAIL"
}
]
}
const vRes =
{
"rows": [
{
"status": "FAIL"
}
]
}
The final value of firstData returned is as following:
{
"total": 2,
"rows": [
{
"hostname": "abc.com",
"ipAddress": "11.11.11.11",
"myservice1Id": "ee0f77c9-ef15",
"type": "type1",
"oRes": {
"status": "PASS"
},
"mylist": {
"listType": "list1",
"createdAt": "2020-12-07"
}
},
{
"hostname": "cde.com",
"ipAddress": "12.12.12.12",
"type": "type2",
"myservice1Id": null,
"vRes": {
"status": "FAIL"
}
}
]
}
Here, one thing to notice is that all the 3 if blocks can be executed in parallel because they are independent of each other.
Can I use Promise.all to execute all the 3 if blocks in parallel?
If yes, how the updated code will look like using Promise.all?

Simplest tweak would be to push each Promise to an array inside the ifs:
const proms = [];
if (hostName !== null && firstData.rows[i].myservice1Id !== null) {
proms.push(
myService2(hostName)
.then(aRes => firstData.rows[i].mylist = aRes[0].dataValues)
);
}
// other ifs changed around the same way
await Promise.all(proms);
You could also make the code easier by making the hostName check only once, and it looks like you're iterating over the whole array, which can be done more easily by invoking the iterator:
try {
const firstData = await myservice1.myservice1Func();
for (const row of firstData.rows) {
const hostName = row.hostname;
if (hostName === null) continue;
const proms = [];
if (row.myservice1Id !== null) {
proms.push(
myService2(hostName)
.then(aRes => row.mylist = aRes[0].dataValues)
);
}
// etc

Hi you have bit of code alterations,
for(let i=0; i<firstData.total; i++){
const hostName = firstData.rows[i]['hostname'];
//check if condition inside the service and return a null (a promise)
Promise.all([myService2(hostName), myService3(hostName), myService4(hostName)]).then((values) => {
console.log(values);
//[resutl1,null,result3]
});
}
Now the problem here is you have to wait until the slowest iteration to complete,
You can fix that with promise pool use,
#supercharge/promise-pool
MDN Promise Medium Blog Source

Related

Convert the simple fetch API code to ".then" notation code

How can I covert following code into .then notation. I wanted to strictly use ".then" notation. That is what I observed with my system.
I raised one question with similar kind of request however, I got the code using async/await. Rather than asking the new requirement over the same thread I initiated this new thread.
Apology for inconvenience. I should have posted this it in first thread itself. Kindly help.
var obj = [{"Id":"10101","descr":"server1.com"},{"Id":"10102","descr":"server2.com"},{"Id":"10103","descr":"server3.com"},{"Id":"10104","descr":"server4.com"},{"Id":"10105","descr":"server5.com"},{"Id":"10106","descr":"server6.com"},{"Id":"10107","descr":"server7.com"}];
var temp = [];
for (var i = 0; i < obj.length; i++){
var id = obj[i].Id;
let response = await fetch('https://abced.com/api/'+id+'/value', {method : "GET", headers: {"Authorization": "xyz"}});
var data = await response.json();
var stats = data.status;
if (stat != "OK")
{
temp.push({Id:obj[i].Id, descr:obj[i].descr, value:"ERROR"})
}
}
console.log(temp);
My expected output is, (values of Id and descr will depends on "if statement" in the code)
[{"Id": "10101","descr": "server1.com","status": "ERROR"},
{"Id": "10103","descr": "server3.com","status": "ERROR"},
{"Id": "10104","descr": "server4.com","status": "ERROR"}]
I tried following but in my system compiler says, "Function declared within loops referencing an outer scope variable mat lead to confusing semantics (Id, descr)"
function fetchMock(url) {
let id = url.split('/')[4];
if ([10101, 10103, 10104].includes(+id)) {
return Promise.resolve({
json() {
return Promise.resolve({
status: 'BAD'
});
}
});
} else {
return Promise.resolve({
json() {
return Promise.resolve({
status: 'OK'
});
}
});
}
}
var obj = [{
"Id": "10101",
"descr": "server1.com"
},
{
"Id": "10102",
"descr": "server2.com"
},
{
"Id": "10103",
"descr": "server3.com"
},
{
"Id": "10104",
"descr": "server4.com"
},
{
"Id": "10105",
"descr": "server5.com"
},
{
"Id": "10106",
"descr": "server6.com"
},
{
"Id": "10107",
"descr": "server7.com"
}
];
function getResults() {
const results = [];
for (let {
Id,
descr
} of obj) {
fetchMock('https://abced.com/api/' + Id + '/value', {
method: "GET",
headers: {
"Authorization": "xyz"
}
}).then(res => res.json()).then(function(data) {
if (data.status !== 'OK') {
results.push({
Id,
descr,
value: 'ERROR'
});
}
});
}
return results;
}
function test() {
const results = getResults();
return results;
}
test();

API Call Before Next Iteration Starts in Loop

I would like to send a POST request to a certain app through their API. What I am trying to do is to process the input data (called data) and send a POST request on one record by one record in the loop. Then, I delete the corresponding object in data for optimization purpose. I know that because of the asynchronous feature of JavaScript, the loop finishes before the function gets called. However, even though I wrap the api function in IIFE or wrap it in an async function with await(the code is below), the compiler still gives me function calls with the same parameter which is the last object. So, when I see created records on the app, David's information was generated three times. The screenshot below is each record object after being processed. If you could tell me ways of triggering the api call before the next iteration in the loop, that would be greatly appreciated.
const obj = [];
var record = {};
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
var people = data;
createKeyValue = ((key, value) => {
var temp = {};
temp["value"] = value;
obj[key] = temp;
});
apiCall = ((record) => {
clientInformation.record.addRecord.then((resp) => {
console.log(resp);
}).catch((err) => {
console.log(err);
});
});
async function asyncFunction(record) {
let promise = new Promise((resolve, reject) => {
setTimeout(() => apiCall(record), 1000)
});
let result = await promise;
console.log(result);
}
while (dataLen > 0) {
for (let [key, value] of Object.entries(data[0])) {
switch(key) {
case 'userId':
createKeyValue(key, value);
break;
case 'name':
createKeyValue(key, value);
break;
default:
}
}
record["record"] = obj;
asyncFunction(record);
data.shift();
dataLen -= 1;
}
Here is the screenshot of how each processed data looks like.
I think you haven't understand how the for loop inside the while works. The data should be incremented each time to get the next array inside data.
The data[0] => { userId: 123 ... }, data[1] => { userId: 345 ... } and so on .
At each for loop iteration checks the 3 elements of each sub array, so each time temp stores the key values for userId and name. So when the loop finishes, the temp contains as key => userId, name and the corresponding values.
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
let i = 0 ;
while ( i < dataLen) {
let temp = [];
for (let [key, value] of Object.entries(data[i])) {
if(key == 'userId' || key == 'name'){
temp[key] = value;
}
}
//Just to print the values and understand
for(let k in temp){
console.log(k+" -> "+temp[k]);
}
//here you will pass the temp values to functions
console.log(" At each iteration execute the required functions ");
//asyncFunction(temp);
i += 1;
}

Return 1 array of several api call

Is it possible to create a function who return an array of result of several api call ?
Instead of this :
var func1;
var func2;
var func3;
apicall1().then((res) => {
func1 = res;
});
apicall1("string").then((res) => {
func2 = res;
});
apicall1(int).then((res) => {
func3 = res;
});
Have something like this :
var result = [];
var Json = "{
"functions": [{
"name": "apicall1",
"args": null
}, {
"name": "apicall2",
"args": "string"
}, {
"name": "apicall2",
"args": [0, "string"]
}]
}";
MyFunction(Json) {
for (i = 0; i < functions.lenght; i += 1) {
functions[i].name(functions[i].args).then((res) => { result.push(res); });
}
return result;
}
I juste search something to avoid to have X callapi one behind the other.
Thanks ;D
You can use Promise.all to get results in an array:
Promise.all([apicall1(), apicall1("string"), apicall1(int)])
.then(results => {
// Destructure the results into separate variables
let [func1, func2, func3] = results;
//access the results here
});
You should use
Promise.all([ api1,api2,api2,...]).then(function(results){
}).catch(function(err){
})
result will be an array with all responses in respective indexes. Here one thing you need to handle is exception. if any of the API call occurs in any kind of exception it will go to catch.
If you want to trigger the calls one after the other, you can use async/await :
https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9
let result = [];
let funcs = [{
"name": "apicall1",
"args": null
}, {
"name": "apicall2",
"args": "string"
}, {
"name": "apicall2",
"args": [0, "string"]
}]
async makeCalls() {
for (let func of funcs) {
let res = await func.name(func.args)
result.push(res)
}
return result;
}
makeCalls()

Search for a related json data

How can i find data that is related to the already known data?
( I'm a newb. )
For example here is my json :
[
{ "id": "1", "log": "1","pass": "1111" },
{ "id": 2, "log": "2","pass": "2222" },
{ "id": 3, "log": "3","pass": "3333" }
]
Now i know that "log" is 1 and i want to find out the data "pass" that is related to it.
i've tried to do it so :
The POST request comes with log and pass data , i search the .json file for the same log value and if there is the same data then i search for related pass
fs.readFile("file.json", "utf8", function (err, data) {
var jsonFileArr = [];
jsonFileArr = JSON.parse(data); // Parse .json objekts
var log = loginData.log; // The 'log' data that comes with POST request
/* Search through .json file for the same data*/
var gibtLog = jsonFileArr.some(function (obj) {
return obj.log == log;
});
if (gotLog) { // If there is the same 'log'
var pass = loginData.pass; // The 'pass' data that comes with POST request
var gotPass = jsonFileArr.some(function (obj) {
// How to change this part ?
return obj.pass == pass;
});
}
else
console.log("error");
});
The problem is that when i use
var gotPass = jsonFileArr.some(function (obj) {
return obj.pass == pass;
});
it searches through the whole .json file and not through only one objekt.
Your main problem is that .some() returns a boolean, whether any of the elements match your predicate or not, but not the element itself.
You want .find() (which will find and return the first element matching the predicate):
const myItem = myArray.find(item => item.log === "1"); // the first matching item
console.log(myItem.pass); // "1111"
Note that it is possible for .find() to not find anything, in which case it returns undefined.
The .some() method returns a boolean that just tells you whether there is at least one item in the array that matches the criteria, it doesn't return the matching item(s). Try .filter() instead:
var jsonFileArr = JSON.parse(data);
var log = loginData.log;
var matchingItems = jsonFileArr.filter(function (obj) {
return obj.log == log;
});
if (matchingItems.length > 0) { // Was at least 1 found?
var pass = matchingItems[0].pass; // The 'pass' data that comes with the first match
} else
console.log("error"); // no matches
Using ES6 Array#find is probably the easiest, but you could also do (among other things)
const x = [{
"id": "1",
"log": "1",
"pass": "1111"
}, {
"id": 2,
"log": "2",
"pass": "2222"
}, {
"id": 3,
"log": "3",
"pass": "3333"
}];
let myItem;
for (let item of x) {
if (item.log === '1') {
myItem = item;
break;
}
}
console.log(myItem);

Better pattern for accessing nested data in Javascript

I am writing some code which iterates first through an array, and then further iterates through an array contained in the original array.
I am ending up with this weird pattern which I am repeating and I am certain is not optimized. While iterating through the last rssFeeds array item, it changes the value of 'triggerCallback' to true. Later, while iterating through the item array, a conditional checks if both triggerCallback is true and the items array is iterating through its last item, at which point it triggers a callback to be in used in async.js's waterfall pattern.
function countSuccessPosts(rssFeeds, cb){
var successCounter = 0;
var triggerCallback = ''
rssFeeds.forEach(function(feed, index, array){
if(index == array.length - 1){
triggerCallback = 'true'
}
feed.itemsToPost.forEach(function(item, itemIndex, itemArray){
if(item.response.id){
++successCounter
}
if(itemIndex == itemArray.length - 1 && triggerCallback == 'true'){
cb(null, rssFeeds, successCounter)
}
})
})
}
What's a more optimal way to structure this pattern?
Data Structure: RssFeeds will have up to 5 different itemsToPost elements.
[
{
"_id": "55808127b8f552c8157f74a7",
"name": "",
"imageUrl": "",
"url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_law.xml",
"latestDate": "1434056400000",
"endpoints": [
{
"_id": "554f9319bc479deb1757bd2e",
"name": "Wise Individ",
"id": 26413291125,
"type": "Group",
"__v": 0
}
],
"__v": 1,
"itemsToPost": [
{
"title": "Aριθμ.: Υ194/12.6.2015 Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
"summary": "Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
"url": "http://www.taxheaven.gr/laws/circular/view/id/21113",
"published_at": 1434056400000,
"time_ago": "5 days ago",
"guid": {
"link": "http://www.taxheaven.gr/laws/circular/view/id/21113",
"isPermaLink": "true"
}
}
]
},
{
"_id": "558093013106203517f96d9c",
"name": "",
"imageUrl": "",
"url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_new.xml",
"latestDate": "1434489601236",
"endpoints": [],
"__v": 0,
"itemsToPost": [
{
"title": "Taxheaven - Άμεση ενημέρωση - Έγκαιρη επιστημονική κωδικοποίηση - Καινοτομικά εργαλεία. Κωδικοποιήθηκαν όλοι οι νόμοι στους οποίους επιφέρει αλλαγές ο νόμος 4330/2015",
"summary": {},
"url": "http://www.taxheaven.gr/news/news/view/id/24088",
"published_at": 1434494400000,
"time_ago": "about 4 hours ago",
"guid": {
"link": "http://www.taxheaven.gr/news/news/view/id/24088",
"isPermaLink": "true"
}
}
]
}
]
I didn't check this but it is pretty similar to what I'm currently using in my project:
function countSuccessPosts(rssFeeds, cb){
async.each(rssFeeds, function(eachFeed, outerCallback) {
async(eachFeed.itemToPost, function(eachItem, innerCallback) {
if(item.response.id) {
//Do Something That Is Actually Async. Could be asking the server for success flag, for instance.
doSomethingThatIsActuallyAsync(item.response.id).then(function (err) {
if (!err) {
successCounter = successCounter + 1;
}
innerCallback();
});
} else { //This could be to skip "empty responses" without the need to go to the server, right?
innerCallback();
}
}, outerCallback);
}, function() {
//All done
cb(null, rssFeeds, successCounter);
});
}
As others mentioned, you need this only if you have actual async methods calls inside the inner loop.
You don't need to keep track of the last item. Just call the callback after both loops exit. I also changed the .forEach to for loops as these execute faster.
function countSuccessPosts(rssFeeds, cb) {
var index, itemIndex, feed, item;
for (index = 0; index < rssFeeds.length; index++) {
feed = rssFeeds[index];
for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
item = feed.itemsToPost[itemIndex];
if(item.response && item.response.id) {
successCounter++;
}
}
}
cb(null, rssFeeds, successCounter);
}
Of course, if you'd rather call countSuccessPosts without a callback the calling code can look like:
var successPosts = countSuccessPosts(rssFeeds);
And you can reformat the function to look like this:
function countSuccessPosts(rssFeeds) {
var index, itemIndex, feed, item, successCounter = 0;
for (index = 0; index < rssFeeds.length; index++) {
feed = rssFeeds[index];
for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
item = feed.itemsToPost[itemIndex];
if(item.response && item.response.id) {
successCounter++;
}
}
}
return successCounter;
}
Wait, why are you using a callback when you can read the data synchronously?
After you've updated your question, it looks like you're just summing a
number of elements in an array
Here's a fully synchronous version that counts the number of itemsToPost that have a valid response.id set.
function countSuccessPosts(rssFeeds) {
return rssFeeds.reduce(function(sum, x) {
return sum + x.itemsToPost.filter(function(y) {
return !!y.response.id;
}).length;
}, 0);
}
If you're required to inject this into an async control flow, you can easily put a wrapper on it
function(rssFeeds, done) {
done(null, rssFeeds, countSuccessPosts(rssFeeds));
}
The point tho, is that countSuccessPosts has a synchronous API because everything that happens within that function is synchronous.

Categories

Resources