How to make async/wait inside of for loop? - javascript

I'm using async await inside of for loop as below.
for (let i = 0; i < result.length; i += 1) {
try{
const client = await axios.get(
`${process.env.user}/client/${result[i].id}`
);
} catch(error){
console.log(error)
}
if (client.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}
But I want to make this using 'new Promise' and 'promiss.all' to make it asynclously.
But I don'k know how to make this correctly doing error handle well.
Could you recommend some advice for this? Thank you for reading it.

This can be a basic solution, i think
let playList = []
for (let i = 0; i < result.length; i += 1) {
playList.push(axios.get(
`${process.env.user}/client/${result[i].id}`
).then(res => {
if (res.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}).catch(ex => console.log(ex)));
}
await Promise.all(playList)
This can also be done by using a foreach loop.
The for/foreach loop can be simplified by using a map function.
Js map function is equivalent of c# select linq function.
The fat arrow in js map function is not bound to return a value unlike c# select inner function which must return a value.
await Promise.all(result.map(async r => {
let client;
try {
client = await axios.get(`${process.env.user}/client/${r.id}`);
} catch (error) {
console.log(error)
}
if (client.data.success === true) {
r.Name = rider.data.client.Name;
r.PhoneNumber = rider.data.client.Number;
}
}));

Try this
var promises = result.map(r => axios.get(`${process.env.user}/client/${r.id}`);
Promise.all(promises).then(function(values) {
console.log('All promises done');
});
The idea is that if you are awaiting something, that is promise, you can await it, or call it to get promise
Example:
function Foo()
{
return new Promise(...); // Promise of int for example
}
you can do
var p = Foo(); //you will get promise
Or
var v = await Foo(); // you will get int value when promise resolved

This is how you do it with async/await + Promise.all:
const myResult = await Promise.all(result.map(({ id }) => {
return axios.get(`${process.env.user}/client/${id}`);
}));
// deal with the result of those requests
const parsed = myResult.map(data => /* your code here */);

Here is an example using Array.map to call your function along with Promise.all. I wrapped the axios request in a function so if one of your request fails, it wont stop every other requests. If you don't mind stopping when you got an issue, look at others answers to your question.
function fakeRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: {
success: true,
client: {
Name: 'Todd',
Number: 5,
},
},
});
}, 300);
});
}
(async() => {
const result = [{}, {}, {}];
await Promise.all(result.map(async(x, xi) => {
try {
const client = await fakeRequest();
if (client.data.success === true) {
result[xi].Name = client.data.client.Name;
result[xi].PhoneNumber = client.data.client.Number;
}
} catch (err) {
console.log(err)
}
}));
console.log(result);
})();

Related

How to push failed promises to separate array?

I am looping through ids that sends it to an async function and I want to return both success and failed data, right now I am only returning success data
const successContractSignature: LpContractSla[] = [];
for (const id of lpContractSlaIds) {
const data = await createLpSignature(context, id);
if (data) {
successContractSignature.push(data);
}
}
return successContractSignature;
If the createLpSignature throws error, do i need a try catch here? but wouldnt that exit the loop?
How can I push failed data to separate array without breaking the loop?
Unless there's a specific reason to avoid the call in parallel, it's always a good practice to avoid to start async calls in a for loop with await, since you are going to wait for each promise to resolve (or reject ) before executing the next one.
This is a better pattern which lets you also get all the results of the promises, either they resolved or rejected:
const successContractSignature: LpContractSla[] = await Promise.allSettled(lpContractSlaIds.map((id: string) => createLpSignature(context,id)))
return successContractSignature;
But if for some particular reason you need to make these calls in a sequence and not in parallel, you can wrap the single call in a try catch block ,that won't exit the loop:
for (const id of lpContractSlaIds) {
let data;
try {
data = await createLpSignature(context, id);
} catch(e) {
data = e
}
if (data) {
successContractSignature.push(data);
}
}
You can test it in this example:
const service = (id) =>
new Promise((res, rej) =>
setTimeout(
() => (id %2 === 0 ? res("ID: "+id) : rej('Error ID : '+id)),
1000
)
);
const ids = [1,2,3,4,5]
const testParallelService = async () => {
try {
const data = await Promise.allSettled(ids.map(id => service(id)))
return data.map(o => `${o.status}: ${o.reason ?? o.value}`)
} catch(e) {
console.log(e)
}
}
testParallelService().then(data => console.log("Parallel data: ", data))
const testSequentialService = async () => {
const res = [];
for (const id of ids) {
let data;
try {
data = await service(id);
} catch (e) {
data = e;
}
if (data) {
res.push(data);
}
}
return res;
};
testSequentialService().then((data) => console.log('Sequential Data: ', data));

Wait loop to finish first - Typescript (Angular)

So I have this code here triggered using click on HTML page:
public salaryConfirmation() {
const matDialogConfig: MatDialogConfig = _.cloneDeep(GajiIdSettings.DIALOG_CONFIG);
this.warningNameList = [];
for(let i=0; i < this.kelolaDataPenggajianInfoDataKaryawanList.length; i++) {
const positionClassId = this.selectedKaryawanAllData[i].position.positionClass.id;
const beginYearMonth = this.inputForm.get('bulanBerlaku').value;
const gajiPokok = this.kelolaDataPenggajianInfoDataKaryawanList[i].gaji;
this.structureAndSalaryScaleValidationService.getSalaryRange(positionClassId, beginYearMonth, gajiPokok)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(
async (result) => {
this.uiBlockService.hideUiBlock();
if(result.status == 'warning') {
if(result.warnings[0].code == 'trxMutasiKaryawan.confirmation.alert') {
await this.warningNameList.push(this.kelolaDataPenggajianInfoDataKaryawanList[i]);
}
}
},
(error) => {
this.uiBlockService.hideUiBlock();
this.contentAlertService.error(error.errors);
},
() => { this.uiBlockService.hideUiBlock(); }
)
}
matDialogConfig.data = this.warningNameList;
console.log("this.warningNameList.length :", this.warningNameList.length);
if (this.warningNameList.length > 0) {
this.save();
} else {
this.inputMassalGajiWarningComponentDialogRef = this.dialog.open(InputMassalGajiWarningComponent, matDialogConfig);
this.inputMassalGajiWarningComponentDialogRef.afterClosed().subscribe(
(confirm: boolean) => {
if (confirm) {
this.save();
}
}
);
}
}
The problem is, I tried to catch the length of this.warningNameList variable. But it always shows "0" on the result.
I know the problem is because this should be work in asynchronously. But I don't know how to apply it in typescript. Been search this case but I always failed to apply. Anyone can help me out?
I already put await inside the loop, but seems it's not working because of wrong placement.
Some reference that I found out there is this => JavaScript async and await in loops
Thanks in advance
Putting await inside loop is not gonna help you. as it is running in a different context already.
What you need to do is probably chain this 2 operations one after another after using promise instead of observable here.
you can probably do something like this,
public async salaryConfirmation() {
const matDialogConfig: MatDialogConfig = _.cloneDeep(GajiIdSettings.DIALOG_CONFIG);
this.warningNameList = [];
for(let i=0; i < this.kelolaDataPenggajianInfoDataKaryawanList.length; i++) {
const positionClassId = this.selectedKaryawanAllData[i].position.positionClass.id;
const beginYearMonth = this.inputForm.get('bulanBerlaku').value;
const gajiPokok = this.kelolaDataPenggajianInfoDataKaryawanList[i].gaji;
let result = await this.structureAndSalaryScaleValidationService.getSalaryRange(positionClassId, beginYearMonth, gajiPokok)
.pipe(takeUntil(this.ngUnsubscribe)).toPromise();
// Transform following data
// .subscribe(
// async (result) => {
// this.uiBlockService.hideUiBlock();
// if(result.status == 'warning') {
// if(result.warnings[0].code == 'trxMutasiKaryawan.confirmation.alert') {
// await this.warningNameList.push(this.kelolaDataPenggajianInfoDataKaryawanList[i]);
// }
// }
// },
// (error) => {
// this.uiBlockService.hideUiBlock();
// this.contentAlertService.error(error.errors);
// },
// () => { this.uiBlockService.hideUiBlock(); }
// )
}
matDialogConfig.data = this.warningNameList;
console.log("this.warningNameList.length :", this.warningNameList.length);
if (this.warningNameList.length > 0) {
this.save();
} else {
this.inputMassalGajiWarningComponentDialogRef = this.dialog.open(InputMassalGajiWarningComponent, matDialogConfig);
this.inputMassalGajiWarningComponentDialogRef.afterClosed().subscribe(
(confirm: boolean) => {
if (confirm) {
this.save();
}
}
);
}
}
Just like above code convert observables to promise, don't subscribe them.
This way you can use async await syntax with obervables as well, although find out how to handle errors this way.

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

How to use async-await with Promise.all()?

At the moment I am using this code below to get the results of several Promises using async await:
let matchday = await createMatchday(2018, 21, [/*9 matches of matchday*/]);
//Further calculations
async function createMatchday(seasonNr, matchdayNr, matches) {
let md = new Matchday(seasonNr, matchdayNr, matches);
await md.getStandings(seasonNr, matchdayNr);
return md;
}
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
}
async getStandings(seasonNr, matchdayNr) {
let promiseArr = [];
promiseArr.push(makeHttpRequestTo(`http://externService.com/standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
promiseArr.push(makeHttpRequestTo(`http://externService.com/homestandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
promiseArr.push(makeHttpRequestTo(`http://externService.com/awaystandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
promiseArr.push(makeHttpRequestTo(`http://externService.com/formstandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
let resulArr = await Promise.all(promiseArr);
this.standings = resultArr[0];
this.homeStandings = resultArr[1];
this.awayStandings = resultArr[2];
this.formStandings = resultArr[3];
}
}
function makeHttpRequest(url) {
return new Promise((resolve, reject) => {
//AJAX httpRequest to url
resolve(httpRequest.responseText);
}
}
Is this actually the best way to read the values of several promises where the promises don't need to wait for each other to end but rather work at the same time by using Promise.all() or is there a better way to make e.g. several httpRequests at the same time because this seems quite repetetive?
Your URLs all follow the same sort of pattern, so you can greatly reduce your code by mapping an array of ['', 'home', 'away', 'form'] to the URLs. Then, map those URLs to Promises through makeHttpRequestTo, and then you can destructure the awaited results into the this. properties:
async getStandings(seasonNr, matchdayNr) {
const urls = ['', 'home', 'away', 'form']
.map(str => `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
const promiseArr = urls.map(makeHttpRequestTo);
[
this.standings,
this.homeStandings,
this.awayStandings,
this.formStandings
] = await Promise.all(promiseArr);
}
To populate each property individually rather than waiting for all responses to come back:
async getStandings(seasonNr, matchdayNr) {
['', 'home', 'away', 'form']
.forEach((str) => {
const url = `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`;
makeHttpRequestTo(url)
.then((resp) => {
this[str + 'Standings'] = resp;
});
});
}
If you don't want to wait for all requests to complete before continuing the execution flow, you could make the properties of the class be promises:
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
let url = `http://externService.com/${propertyName.toLowerCase()}`
+ `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
this[propertyName] = makeHttpRequestTo(url)
});
}
}
Test using the following snippet
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
let url = `http://externService.com/${propertyName.toLowerCase()}`
+ `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
this[propertyName] = makeHttpRequestTo(url)
});
}
}
/**************************************
* Test harness
**************************************/
function makeHttpRequestTo(url) {
// Fake an AJAX httpRequest to url
const requested_resource = url.match('^.*\/\/.*\/([^?]*)')[1];
const fake_response_data = 'data for ' + url.match('^.*\/\/.*\/(.*)$')[1];
let delay = 0;
let response = '';
switch (requested_resource) {
// To make it interesting, let's give the 'standings' resource
// a much faster response time
case 'standings':
delay = 250;
break;
case 'homestandings':
delay = 2000;
break;
case 'awaystandings':
delay = 3000;
break;
case 'formstandings':
delay = 4000; // <== Longest request is 4 seconds
break;
default:
throw (util.format('Unexpected requested_resource: %s', requested_resource));
}
return new Promise((resolve, reject) => {
setTimeout(() => resolve(fake_response_data), delay);
});
}
async function testAccessingAllProperties() {
const testId = "Test accessing all properties";
console.log('\n%s', testId);
console.time(testId)
let md = new Matchday(2018, 21, []);
console.log(await md.standings);
console.log(await md.homeStandings);
console.log(await md.awayStandings);
console.log(await md.formStandings);
console.timeEnd(testId)
}
async function testAccessingOnlyOneProperty() {
const testId = `Test accessing only one property`;
console.log('\n%s', testId);
console.time(testId)
let md = new Matchday(2018, 21, []);
console.log(await md.standings);
console.timeEnd(testId)
}
async function all_tests() {
await testAccessingAllProperties();
await testAccessingOnlyOneProperty();
}
all_tests();
Conclusion
The above snippet shows that the execution time is not penalized by properties that are not accessed. And execution time of accessing all properties is no worse than using promise.all.
You'd just need to remember to use await when accessing those properties.
To answer, No you shouldn't block other XHR or any I/O request which are not dependent on each other. I would have written your function like this;
const getFavourites = async () => {
try {
const result = await Promise.resolve("Pizza");
console.log("Favourite food: " + result);
} catch (error) {
console.log('error getting food');
}
try {
const result = await Promise.resolve("Monkey");
console.log("Favourite animal: " + result);
} catch (error) {
console.log('error getting animal');
}
try {
const result = await Promise.resolve("Green");
console.log("Favourite color: " + result);
} catch (error) {
console.log('error getting color');
}
try {
const result = await Promise.resolve("Water");
console.log("Favourite liquid: " + result);
} catch (error) {
console.log('error getting liquid');
}
}
getFavourites();
This way every async functions will be called at once, and no async action will block the other action.
To create a Promise you need to call new Promise((resolve, reject) => { return "Pizza"; })
You're doing it the right way
If you want you can shorten the code by using an array (and its functions like map, etc...), but it won't improve its performance

Using Promise.all

Firstly apologies if this is a simple problem to solve but I am using node-fetch and the concept of Promises for the first time. I am calling an api endpoint which returns paginated data so i get multiple links for further data.
I am handling this by putting all the links into an array and then I want to call each endpoint in the array and just print out the data to the console for now
So my first call is
var urlArray = [];
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers })
.then(function(response){
return response.json(); // pass the data as promise to next then block
})
.then(function(json){
// Collect Paginated Results
var number_of_pages = parseInt(json.meta['pagination']['total_pages']);
for (i = 2; i < number_of_pages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
})
.catch(function(error) {
console.log(error);
});
So once this done i have an array of urls i need to hit sequentially, could anyone give me some pointers please on how to then setup a promise to hit each one? or can i have something where is do it in my for loop?
Thanks
sorry if this is wrong!
At the beginning, put:
var promise_array = [];
in the for loop, replace with
for (i = 2; i < number_of_pages + 1; i++) {
var url = `${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`;
urlArray.push(url);
promise_array.push(fetch(url));
}
At the end of the function put
Promise.all(promise_array).then(all_responses => {
// .. pluck json, then continue ...
});
You can achieve this by:
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers }).then(response => {
var json = response.json();
var numberOfPages = parseInt(json.meta['pagination']['total_pages']);
var urlArray = [];
for (i = 2; i < numberOfPages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
var promises = urlArray.map(url => getSomethingByUrl(url));
return Promise.all(promises);
}).then(results => {
console.log(results);
});
You can use Array.prototype.reduce to create Promise chain so they will be executed one after another
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});
Edit:
As mentioned by Bergi, value from someAction should be returned in case it's asynchronous (return Promise). If someAction is asynchronous it must return Promise.
"use strict";
function someAction(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve(i);
}, i*1000);
});
}
const urls = [1,2,3,4,5];
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});

Categories