Javascript: await inside of loop issue - javascript

I want to use Eslint plugin in my project with Webpack but it does not let me use await inside the loop.
According to Eslint docs it recommends to remove await from the loop and just add a Promise after.
Wrong example:
async function foo(things) {
const results = [];
for (const thing of things) {
// Bad: each loop iteration is delayed until the entire asynchronous operation completes
results.push(await bar(thing));
}
return baz(results);
}
Correct example:
async function foo(things) {
const results = [];
for (const thing of things) {
// Good: all asynchronous operations are immediately started.
results.push(bar(thing));
}
// Now that all the asynchronous operations are running, here we wait until they all complete.
return baz(await Promise.all(results));
}
But in my code I just merge data into one array which comes from HTTP request:
async update() {
let array = [];
for (url of this.myUrls) {
const response = await this.getData(url);
array = await array.concat(response);
}
}
Is it possible to remove await from this loop and add Promise just for array concatenation? I don't have an idea how to do it...

If you like one-liners.
const array = await Promise.all(this.myUrls.map((url)=> this.getData(url)));
In this case, the map method returns a bunch of promises, based on the URL, and your getData method. The Promise.all waits until all of your promises will be resolved. These promises run parallel.

You can use promise like this:
function update() {
let array = [],req=[];
for (url of this.myUrls) {
req.push(this.getData(url));
}
return Promise.all(req).then((data)=>{
console.log(data);
return data;
})
}

If I'm understanding you correctly, your getData function returns an array?
Using the one-liner supplied by Anarno, we can wait until all promises are resolved and then concatenate all the arrays.
const allResults = await Promise.all(this.myUrls.map((url) => this.getData(url)));
let finalArray = [];
allResults.forEach((item) => finalArray = finalArray.concat(item));

Related

how to call multiple await in a for loop if the one await depends on another awaits data in nodejs

Currently I am trying to call multiple awaits within a for loop, as per the documentation this is a performance heavy, so I was thinking of using promise.all() but the problem I am facing is I need the first awaits data to call the other awaits values with in the for loop and assign the new values in an empty object that I created outside the for loop. Do you know how to use promise.all() to solve this problem?
This is my current code:
const parsedSchema = {}
const arrayOfValues = Object.keys(objectOfValues);
for (let i = 0; i < arrayOfValues.length; i++) {
const arrayOfValuesSchema = (
await getObjectFromExternalAPI(arrayOfValues[i])
).data;
Object.assign(parsedSchema, {
...(await $RefParser.dereference(JSON.parse(arrayOfValuesSchema.toString('utf-8')))).properties
});
}
Update:
https://jsfiddle.net/sy4j6mgu/ this worked for me but I don't know how to simplify from here.
Does this help?
The following will synchronously and rapid-fire create promises for each value in arrayOfValues. Each executor function runs synchronously and tees-up async requests to getObjectFromExternalAPI and then $RefParser.dereference on the result.
The code will wait for all the promises to be fulfilled. Control then moves to synchronous aggregation of the results into a single object using Array#reduce.
const arrayOfValues = Object.keys(objectOfValues)
const promises = arrayOfValues.map((v) => new Promise((resolve) =>
getObjectFromExternalAPI(v)
.then(({ data }) => $RefParser.dereference(data.toString('utf-8')))
.then(resolve)
const results = await Promise.all(promises) // fails fast!
const parsedSchema = results.reduce((acc, { properties }) =>
({ ...acc, ...properties }), {})

How to wait until multiple files are processed before calling finished function js

The following function runs after a drag and drop operation of multiple files.
function getFilesInfo(ev){
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
if (ev.dataTransfer.items[i].kind === 'file') {
let file = ev.dataTransfer.items[i].getAsFile();
//getFileInfo adds string to DOM element
//Note the promise usage ...
file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
}
}
}
I can't figure out how to call a function after all of the promises in this function finish.
Basically I want something like this, sequentially:
getFilesInfo(ev);
//getFileInfo(<file1>);
//getFileInfo(<file2>);
//getFileInfo(<file3>);
// etc.
//run only after all getFileInfo() calls have finished
processResults();
The tricky part is that reading the files generates a promise for each file that gets called when the file has been read into memory (part of the arrayBuffer() call). I can't figure out how to delay processResults because getFilesInfo finishes after all of the read calls have been triggered, not (from what I can tell), after the getFileInfo functions have finished.
It seems like perhaps I could somehow add all arrayBuffer calls to an array and then do some promise chaining (maybe?) but that seems awkward and I'm not even sure how I would do that.
You can use Promise.all to wait for an array of promise to finish:
async function getFilesInfo(ev) {
// create list of jobs
const jobs = [];
for (const item of ev.dataTransfer.items) {
if (item.kind === 'file') {
let file = item.getAsFile();
jobs.push(file.arrayBuffer().then(data => {
getFileInfo(file.name, data);
}));
}
}
// wait for all promise to fullfil
await Promise.all(jobs);
}
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You could do it that way:
function getFilesInfo(ev){
return ev.dataTransfer.items.filter(item=>item.kind === 'file').map(item=>{
let file = item.getAsFile();
return file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
});
}
Promise.all(...getFilesInfo(ev)).then(_=>{
processResults();
});
// or with async/await
(async ()=>{
await Promise.all(...getFilesInfo(ev));
processResults();
})()
async function getFilesInfo(ev) {
await Promise.all(ev.dataTransfer.items.map(async (i) => {
const file = i.getAsFile();
const data = await file.arrayBuffer();
return getFileInfo(file.name, data);
}));
}
await getFilesInfo(ev); // will be awaited until all the promises are resolved
processResults();
Let me know if that helps.
The conceptual hurdle I was running into was that I was thinking of the then function as returning the results, not promises. Also, many of the examples I've seen with Promise.all are usually just concatenating explicit calls, not building an array in a loop.
As suggested by Bergi, I simply added the calls to an array, and then passed that array into Promise.all
function getFilesInfo(ev) {
// create list of jobs
let jobs = [];
for (const item of ev.dataTransfer.items) {
if (item.kind === 'file') {
let file = item.getAsFile();
jobs.push(file.arrayBuffer().then(data => {
getFileInfo(file.name, data);
}));
}
}
return jobs;
}
//The call in the parent
let jobs = getFilesInfo(ev);
Promise.all(jobs).then(processResults);

calling an async function inside for loop in JavaScript / node js

I want to call an async function inside for loop
function test(req,res){
//somelogic part here
async function create(data){
//logic part here
}
for( var value in values){
// some codeing part here
await create(data);
}
}
I am getting this error
uncaughtException: await is only valid in async function
then I can't call an async function inside For loop.
I know it is written like this but is there any possible way I can call an async function inside for loop
for await (var value in values)
Just put async in front of function test.
async function test(req, res) {…
If you want to await a promise, then the function that's using await needs to be async function.
Hi As I understood your concern I would suggest to use .map() function which is very useful in this type of case.
async function create(data) {
// Create user logic here
}
const usernames = ['test1', 'test2', 'test3'];
const createdUsers = usernames.map(async (val) => {
const user = await create();
return user;
});
await Promise.all(createdUsers);
Wrap and create new function inside another function isn't a good ideal. You can move function create to outside like the code bellow
async function create(data){
//logic part here
}
async function test(req,res){
//somelogic part here
for( var value in values){
// some codeing part here
await create(data);
}
}
Or use arrow function instead
async function test(req,res){
//somelogic part here
const create = async (data) => {
//logic part here
}
for( var value in values){
// some codeing part here
await create(data);
}
}
If calling just an asynchronous function is only going to part of your for loop mean you can simple push the promise returned by the asynchronous calls to an array and use promise static methods like 'all'
let pros= []
for(){
pro.push(create(data));
}
Promise.all(pros).then(()=> {
#implement rest or resolve another promise
})
Please refer..
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
The problem is not with your create function, but with your test function. The scope where you call the await syntax, needs to be async. Therefore, your scope here is test.
Simply update your code to the following;
/*vv --> async*/
async function test(req, res) {
console.log("test is called");
async function create(to) {
return new Promise((resolve) => {
setTimeout(() => resolve(Date.now()), to);
});
}
const values = {
a: 1e3,
b: 2e3,
c: 3e3
};
for (var value in values) {
// some codeing part here
const to = values[value];
const result = await create(to);
console.log("result:", result);
}
return 1;
}
(async() => {
const res = await test();
console.log("done");
})();
Your question is a little confusing because there doesn't appear to be any reason to async/await - create doesn't appear to return any data so the aync/await seems redundant, and you can probably remove it.
If, however, create does return a value, and you want to wait until all the values have been returned before you continue, create an array of function calls and then use Promise.all to wait until the promises returned from create have resolved.
// `create` returns a promise that resolves
// after two seconds
function create(data){
return new Promise((res, rej) => {
setTimeout(() => res(data), 2000);
});
}
async function test(req,res) {
const values = [1, 2, 3, 4];
// Create an array of promises
const arr = values.map(el => create(el));
// And use Promise.all to wait to resolve them
const output = await Promise.all(arr);
console.log(output);
}
test();
If you put the Promise in the for loop then it will get a total of every responses time in every iteration. So I'm suggesting the Promise.all for that.
async func() {
let arr = [];
for(var value in values){
arr.push(create(data));
}
let createdArr = await Promise.all(arr);
// Using createdArr, you can get all waited responses
}
In here it will get only the most responding time, not the total. All Promises run at the same time.

for await of VS Promise.all

Is there any difference between this:
const promises = await Promise.all(items.map(e => somethingAsync(e)));
for (const res of promises) {
// do some calculations
}
And this ?
for await (const res of items.map(e => somethingAsync(e))) {
// do some calculations
}
I know that in the first snippet, all the promises are fired at the same time but I'm not sure about the second. Does the for loop wait for the first iteration to be done to call the next promise ? Or are all the promises fired at the same time and the inside of the loop acts like a callback for them ?
Yes, they absolutely are different. for await is supposed to be used with asynchronous iterators, not with arrays of pre-existing promises.
Just to make clear,
for await (const res of items.map(e => somethingAsync(e))) …
works the same as
const promises = items.map(e => somethingAsync(e));
for await (const res of promises) …
or
const promises = [somethingAsync(items[0]), somethingAsync(items[1]), …];
for await (const res of promises) …
The somethingAsync calls are happening immediately, all at once, before anything is awaited. Then, they are awaited one after another, which is definitely a problem if any one of them gets rejected: it will cause an unhandled promise rejection error. Using Promise.all is the only viable choice to deal with the array of promises:
for (const res of await Promise.all(promises)) …
See Waiting for more than one concurrent await operation and Any difference between await Promise.all() and multiple await? for details.
The need for for await ... arises when on an asynchronous iterator the computation of the current iteration depends on some of the previous iterations. If there are no dependences, Promise.all is your choice. The for await construct was designed to work with asynchronous iterators, although - as in your example, you can use it with an array of promises.
See the example paginated data in the book javascript.info for an example using an asynchronous iterator that can't be rewritten using Promise.all:
(async () => {
for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
console.log(commit.author.login);
}
})();
Here the fetchCommits async iterator makes a request to fetch the commits of a GitHub repo. The fetch responds with a JSON of 30 commits, and also provides a link to the next page in the Link header. Therefore the next iteration can only start after the previous iteration has the link for the next request
async function* fetchCommits(repo) {
let url = `https://api.github.com/repos/${repo}/commits`;
while (url) {
const response = await fetch(url, {
headers: {'User-Agent': 'Our script'},
});
const body = await response.json(); // (array of commits
// The URL of the next page is in the headers, extract it using a regexp
let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
nextPage = nextPage?.[1];
url = nextPage;
for(let commit of body) { // yield commits one by one, until the page ends
yield commit;
}
}
}
As you said Promise.all will send all the requests in one go and then you will get the response when all of them gets completed.
In the second scenario, you will send the request in one go but recieve response as for each one by one.
See this small example for reference.
let i = 1;
function somethingAsync(time) {
console.log("fired");
return delay(time).then(() => Promise.resolve(i++));
}
const items = [1000, 2000, 3000, 4000];
function delay(time) {
return new Promise((resolve) => {
setTimeout(resolve, time)
});
}
(async() => {
console.time("first way");
const promises = await Promise.all(items.map(e => somethingAsync(e)));
for (const res of promises) {
console.log(res);
}
console.timeEnd("first way");
i=1; //reset counter
console.time("second way");
for await (const res of items.map(e => somethingAsync(e))) {
// do some calculations
console.log(res);
}
console.timeEnd("second way");
})();
You could try it here as well - https://repl.it/repls/SuddenUselessAnalyst
Hope this helps.
Actually, using the for await syntax does fire the promises all at once.
The small piece of code proves it:
const sleep = s => {
return new Promise(resolve => {
setTimeout(resolve, s * 1000);
});
}
const somethingAsync = async t => {
await sleep(t);
return t;
}
(async () => {
const items = [1, 2, 3, 4];
const now = Date.now();
for await (const res of items.map(e => somethingAsync(e))) {
console.log(res);
}
console.log("time: ", (Date.now() - now) / 1000);
})();
stdout:
time: 4.001
But the inside of the loop doesn't act "as a callback". If I reverse the array, all the logs appear at once. I suppose that the promises are fired at once and the runtime just waits for the first one to resolve to go to the next iteration.
EDIT: Actually, using for await is bad practice when we use it with something other than an asynchronous iterator, best is to use Promise.all, according to #Bergi in his answer.

How to use Promise.all in react js ES6

What i want to do is to upload file on server, then get URL of uploaded file and preview it. Files can be more than one. For that purpose i have written following code:
let filesURL=[];
let promises=[];
if(this.state.files_to_upload.length>0) {
for(let i=0; i<this.state.files_to_upload.length; i++) {
promises.push(this.uploadFilesOnServer(this.state.files_to_upload[i]))
}
Promise.all(promises).then(function(result){
console.log(result);
result.map((file)=>{
filesURL.push(file);
});
});
console.log(filesURL);
}
const uploadedFilesURL=filesURL;
console.log(uploadedFilesURL);
console.log(filesURL); give me the values returned by Promise.all.
And i want to use these values only when Promise.all completes properly. But, i am facing problem that lines console.log(uploadedFilesURL); excutes first irrespective of Promise.all and give me undefined values.I think i am not using promises correctly, can anyone please help me?
uploadFileOnServer code is:
uploadFilesOnServer(file)
{
let files=[];
let file_id='';
const image=file;
getImageUrl().then((response) => {
const data = new FormData();
data.append('file-0', image);
const {upload_url} = JSON.parse(response);
console.log(upload_url);
updateProfileImage(upload_url, data).then ((response2) => {
const data2 = JSON.parse(response2);
file_id=data2;
console.log(file_id);
files.push(file_id);
console.log(files);
});
});
return files;
}
No, promise is asynchronous and as such, doesn't work the way you think. If you want to execute something after a promise completed, you must put it into the promise's then callback. Here is the example based on your code:
uploadFilesOnServer(file) {
let files=[];
let file_id='';
const promise = getImageUrl()
.then((imageUrlResponse) => {
const data = new FormData();
data.append('file-0', file);
const { upload_url } = JSON.parse(imageUrlResponse);
console.log(upload_url);
return updateProfileImage(upload_url, data);
})
.then ((updateImageResponse) => {
file_id= JSON.parse(updateImageResponse);
console.log(file_id);
files.push(file_id);
console.log(files);
return files;
});
return promise;
}
let filesPromise = Promise.resolve([]);
if(this.state.files_to_upload.length > 0) {
const promises = this.state.files_to_upload.map((file) => {
return this.uploadFilesOnServer(file);
});
filesPromise = Promise.all(promises).then((results) => {
console.log(results);
return [].concat(...results);
});
}
// This is the final console.log of you (console.log(uploadedFilesURL);)
filesPromise.then((filesUrl) => console.log(filesUrl));
A good book to read about ES6 in general and Promises in particular is this book Understanding ECMAScript 6 - Nicholas C. Zakas
Edit:
Here is an simple explanation of the example code:
The uploadFilesOnServer is a function that takes a file, upload it and will return the file URL when the upload completes in the future in the form of a promise. The promise will call its then callback when it gets the url.
By using the map function, we create a list of url promises, the results we've got from executing uploadFilesOnServer on each file in the list.
The Promise.all method waits for all the promises in the list to be completed, joins the list of url results and create a promise with the result which is the list of urls. We need this because there is no guarantee that all of the promises will complete at once, and we need to gather all the results in one callback for convenience.
We get the urls from the then callback.
You have to do this on the .then part of your Promise.all()
Promise.all(promises)
.then(function(result){
console.log(result);
result.map((file)=>{
filesURL.push(file);
});
return true; // return from here to go to the next promise down
})
.then(() => {
console.log(filesURL);
const uploadedFilesURL=filesURL;
console.log(uploadedFilesURL);
})
This is the way async code works. You cannot expect your console.log(filesURL); to work correctly if it is being called syncronously after async call to fetch files from server.
Regarding to your code there are several problems:
1.uploadFilesOnServer must return Promise as it is async. Therefore:
uploadFilesOnServer(file)
{
let files=[];
let file_id='';
const image=file;
return getImageUrl().then((response) => {
const data = new FormData();
data.append('file-0', image);
const {upload_url} = JSON.parse(response);
console.log(upload_url);
updateProfileImage(upload_url, data).then ((response2) => {
const data2 = JSON.parse(response2);
file_id=data2;
console.log(file_id);
files.push(file_id);
console.log(files);
return files;
});
});
}
2.Inside your main function body you can assess results of the Promise.all execution only in its respective then handler.
As a side note I would recomment you to use es7 async/await features with some transpilers like babel/typescript. This will greatly reduce the nesting/complications of writing such async code.

Categories