Problems with Promise.all [duplicate] - javascript

This question already has an answer here:
Promise and Promise.all(array) executed before array fulfilled [duplicate]
(1 answer)
Closed 26 days ago.
I have to get back to this forum for help because I can't still make a 'Promise.all' work!
First, I have this function, which is supposed to return a promise:
const myFetch = (a, b) => {
var url;
// some stuff bulding 'url' using a and b
fetch(url).then(response => {
return response.json();
}
})
The idea is that the above function returns a promise, whose value, once resolved, is the json object. I have checked that the json is actually valid. If I replace the "return ..." line with the line below I actually get a valid json:
response.json().then(res=> console.log(res))
Second, I have this for loop, after which I expect to have an array of promises:
promises = [];
for (...){
// some other stuff
promises.push(myFetch(a, b))
}
Eventually I execute this code:
Promise.all(promises)
.then(responses => { // <=== Here I get all "undefined"
responses.forEach(response => {
console.log(response);// <=== Here I get all "undefined"
});
// some other stuff that I can do only after all fetches are complete
})
I expect the .then portion to be executed only once all promises are resolved, and also expect "responses" to be a list of all the json responses from the individual promises above. Still, I get a string of "undefined". The impression is that the portion of code within .then is running even though the promises are not yet resolved.
What did I do wrong? How can I be sure to have all the json objects from the individual fetches before proceeding? (note, I cannot use await/async). Thanks

You need to return the promise from the fetch call, otherwise the promise chain will be broken, once you do this, all should play nice!
Something like so should work:
const myFetch = (a, b) => {
var url;
// some stuff bulding 'url' using a and b
return fetch(url).then(response => {
return response.json();
})
};
A snippet example:
const myFetch = (url, a, b) => {
return fetch(url).then(response => {
return response.json();
})
};
function testMyFetch() {
promises = [];
for(let i = 0; i < 5; i++) {
promises.push(myFetch("https://jsonplaceholder.typicode.com/users/" + (i+1)));
}
Promise.all(promises).then(result => console.log("Promise.all result:", result));
}
testMyFetch();

Related

Resolving Promises sequentially not working

I have a code to:
Read last three data from Firebase
Iterate each retrieved data
Push a Promise-returning function expression to an array of Promise to be processed sequentially later
Process said array sequentially
Code:
firebase.database().ref('someRef').limitToLast(3).on('value', snapshot => {
let promiseArray = [];
snapshot.forEach(e => {
promiseArray.push(() => {
firebase.storage().ref(e.key).getDownloadURL().then(url => {
//Do something with URL
//In this case, I print out the url to see the order of URL retrieved
//Unfortunately, the order was incorrect
return 'Resolved, please continue'; //Return something to resolve my Promise
});
});
});
let result = Promise.resolve([]);
promiseArray.forEach(promise => {
result = result.then(promise);
});
});
I think that it should already be correct. However, the result I want to get is wrong. What did I miss?
EDIT
I seem to have missed a point. In my Promise array, I want the first function to resolve the Promise it returns first before continuing to the second function.
You should use reduce. A very good example you will find here: https://decembersoft.com/posts/promises-in-serial-with-array-reduce/
forEach is a synchronous method. You can use map to create the array of promises and then use promise.all.
firebase.database().ref('someRef').limitToLast(3).on('value', snapshot => {
let promiseArray = [];
const promiseArray = snapshot.map(e => firebase.storage().ref(e.key).getDownloadURL());
Promise.all(promiseArray).then((resultArr) => {
// Do anything with your result array
});
}
For sequential execution of promises you can use async await.
firebase.database().ref('someRef').limitToLast(3).on('value', async (snapshot) => {
let promiseArray = [];
const promiseArray = snapshot.map(e => firebase.storage().ref(e.key).getDownloadURL());
let result;
for(let i = 0; i < promiseArray.length; i++) {
result = await promiseArray[i];
}
});
I figured it out: apparently, I forgot to my function is not returning a Promise. Because of that, when I'm chaining the thens, it's not waiting for my Promise to resolve first as it wasn't even returned in the first place. I'm basically returning a void, thus the functions continue without waiting for the previous Promise to resolve. Simply adding return fixes the problem:
firebase.database().ref('someRef').limitToLast(3).on('value', snapshot => {
let promiseArray = [];
snapshot.forEach(e => {
promiseArray.push(() => {
return firebase.storage().ref(e.key).getDownloadURL().then(url => { //Add return here
//Do something with URL
//In this case, I print out the url to see the order of URL retrieved
//Unfortunately, the order was incorrect
return 'Resolved, please continue'; //Return something to resolve my Promise
});
});
});
let result = Promise.resolve([]);
promiseArray.forEach(promise => {
result = result.then(promise);
});
});

Sequential execution of Promise.all

Hi I need to execute promises one after the other how do I achieve this using promise.all any help would be awesome. Below is the sample of my code I am currently using but it executes parallel so the search will not work properly
public testData: any = (req, res) => {
// This method is called first via API and then promise is triggerd
var body = req.body;
// set up data eg 2 is repeated twice so insert 2, 5 only once into DB
// Assuming we cant control the data and also maybe 3 maybe inside the DB
let arrayOfData = [1,2,3,2,4,5,5];
const promises = arrayOfData.map(this.searchAndInsert.bind(this));
Promise.all(promises)
.then((results) => {
// we only get here if ALL promises fulfill
console.log('Success', results);
res.status(200).json({ "status": 1, "message": "Success data" });
})
.catch((err) => {
// Will catch failure of first failed promise
console.log('Failed:', err);
res.status(200).json({ "status": 0, "message": "Failed data" });
});
}
public searchAndInsert: any = (data) => {
// There are database operations happening here like searching for other
// entries in the JSON and inserting to DB
console.log('Searching and updating', data);
return new Promise((resolve, reject) => {
// This is not an other function its just written her to make code readable
if(dataExistsInDB(data) == true){
resolve(data);
} else {
// This is not an other function its just written her to make code readable
insertIntoDB(data).then() => resolve(data);
}
});
}
I looked up in google and saw the reduce will help I would appreciate any help on how to convert this to reduce or any method you suggest (Concurrency in .map did not work)
the Promises unfortunatelly does not allow any control of their flow. It means -> once you create new Promise, it will be doing its asynchronous parts as they like.
The Promise.all does not change it, its only purpose is that it checks all promises that you put into it and it is resolved once all of them are finished (or one of them fail).
To be able to create and control asynchronous flow, the easiest way is to wrap the creation of Promise into function and create some kind of factory method. Then instead of creating all promises upfront, you just create only one promise when you need it, wait until it is resolved and after it continue in same behaviour.
async function doAllSequentually(fnPromiseArr) {
for (let i=0; i < fnPromiseArr.length; i++) {
const val = await fnPromiseArr[i]();
console.log(val);
}
}
function createFnPromise(val) {
return () => new Promise(resolve => resolve(val));
}
const arr = [];
for (let j=0; j < 10; j++) {
arr.push(createFnPromise(Math.random()));
}
doAllSequentually(arr).then(() => console.log('finished'));
PS: It is also possible without async/await using standard promise-chains, but it requires to be implemented with recursion.
If anyone else cares about ESLint complaining about the use of "for" and the "no await in loop" here is a typescript ESLint friendly version of the above answer:
async function runPromisesSequentially<T>(promises: Array<Promise<T>>):Promise<Array<T>> {
if (promises.length === 0) return [];
const [firstElement, ...rest] = promises;
return [await firstElement, ...(await runPromisesSequentially(rest))];
}
You can then just replace Promise.all by runPromisesSequentially.
#lmX2015's answer is close but it's taking in promises that have already started executing.
A slight modification fixes it
export async function runPromisesSequentially<T>(functions: (() => Promise<T>)[]): Promise<T[]> {
if (functions.length === 0) {
return [];
}
const [first, ...rest] = functions;
return [await first(), ...(await runPromisesSequentially(rest))];
}

Promise returning undefined and status pending

I'm adding some functions to an Angular App and here is the thing: I'm trying to use a function that creates a promise to get data from the server, but every time I try to use it, it returns undefined. I've "debugged" with console.log printing my variable with the result of my function as value and it prints Promise{'pending'}
Here is the function of the promise and the variable I'm trying to assign.
all_allies(text = ''){
return new Promise((resolve, reject) => {
const _text = text ? /${text} : ''
const path = `${this.env.apiPath}/all_allies${_text}`
this.$http
.get(path)
.then(response => {
const { data } = response
resolve(data)
return data;
})
.catch(error => reject(error))
})
Variable
let allies = this.AliadosFactory.all_allies();
As you can see the function and the variable are in different scripts.
I've tried using await reserved word but still doesn't work
Can you try this method?
let allies = await this.AliadosFactory.all_allies();
console.log(allies);
or like this?
this.AliadosFactory.all_allies().then(allies => console.log(allies);
I sure it should work,
Hope this helps.
Have nice day :)
That's because when you perform assignment the Promise is not resolved / rejected yet.
There are two simple solutions:
1. Using then()
this.AliadosFactory.all_allies().then(result => console.log(result));
2. Using async/await
(note that you need an async method in your class)
async foo() {
let allies = await this.AliadosFactory.all_allies();
console.log(allies);
}
Also in all_allies() you don't need to return the value after calling the resolve() method;

Async await with a forEach loop still running asynchronously [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
First of all I did read through similar questions, and still cannot see where I'm making my mistake.
Here's my code:
async function validateWebsites(website) {
var result = url.parse(`http://${website}`);
console.log(result.hostname);
return await fetch(`http://www.${result.hostname}`)
.then(() => console.log(true))
.catch(() => console.log(false));
}
var wrongWebsites = [];
var wrongWebsites = [];
var i = 0;
websites.forEach(website => {
i++;
if (validateWebsites(website) === false
) {
wrongWebsites.push(i);
}
});
console.log(wrongWebsites);
How it works:
The user passes an array of websites, and I want to validate if they're valid websites, not to waste resources and block other errors. Now to the console:
digitlead.com
google.com
georgiancollege.ca
youtube.com
[]
true
true
true
true
So as you see, it prints out first the websites array, and then the response. So it's still async. How do I make it wait? I changed the loop from a for to forEach as suggested by many posts, I used the await and I am returning a promise. So what else do I have to do?
Edit:
I tried to do this:
async function validateWebsites(website) {
var result = url.parse(`http://${website}`); // TODO figure out if filtering all the subpages is a good idea.
console.log(result.hostname);
return await fetch(`http://www.${result.hostname}`)
.then(()=>console.log(true))
.catch(()=>console.log(false));
}
But it doesn't change anything
I found a function called readFileSync. That's more or less what I'm looking for, but with the ability to call a different website.
Here is how you can get all valid websites.
Problem with your validateWebsites function is that it returns promise wchih is resolving to undefined thanks to promise chaining and your loging
Also using forEach to filter array is unnesesery.
But if you wanted you could do something like this
websites.forEach(async website => {
i++;
if (await validateWebsites(website) === false) { // now value is Boolean instead of Promise
wrongWebsites.push(i);
}
});
Also note that if you use global i with asyncronous functions to keep track of index this can lead to many errors.
However I think this soultion should satisfy you
async function validateWebsites(website) {
var result = url.parse(`http://${website}`)
return fetch(`http://www.${result.hostname}`)
.then(() => true) // async function returns promise
.catch(() => false)
}
const websites = ['digitlead.com',
'google.com',
'georgiancollege.ca',
'youtube.com',
'111.1',
'foobarbaz']
async function filter(array, func) {
const tmp = await Promise.all( // waits for all promises to resolve
array.map(func) // evecutes async function and stores it result in new array then returns array of promises
)
return array.filter((_, i) => tmp[i]) // removes invalid websites
}
const validWebsites = filter(websites, validateWebsites)
validWebsites.then(console.log)
Get the indexes of the non-valid sites
async function filter(array, func) {
const tmp = await Promise.all(array.map(func))
return tmp
.map((x, i) => !x && i) // flip true to false and asign index when x is false
.filter(x => x !== false) // return indexes
}
destoryeris saying you should do something like this:
websites.forEach(async website => {
i++;
if (await validateWebsites(website) === false
) {
wrongWebsites.push(i);
}
});
But that alone is problematic because you have wrap your async functions in a try/catch to handle their errors. So something more like this:
websites.forEach(async website => {
i++;
try {
const validSites = await validateWebsites(website);
if (validSites === false) {
wrongWebsites.push(i);
}
} catch(e) {
// handle e
}
})

How to return new variable after some promise process?

getCategory().then((res) => {
let itemList = res;
return new Promise((resolve, reject) => {
for(let i=0; i < res.length; i++ ) {
getItems(res[i].url).then((itempage) => {
const $ = itempage;
const pagination = $('.pagination .page').toArray().length;
itemList[i].total_page = pagination;
});
}
resolve(itemList);
});
}).then((res) => {
console.log(res);
});
Above is my code. First of all, I assign res to a new variable called itemList which is an object array.
Then I do some functions with promise and try to add new data to itemList.
However, when the console.log(res) still prints out original itemList.
I know this may caused by promise function but how to fix it?
Try this instead
getCategory().then(itemList => {
return Promise.all([itemList, Promise.all(itemList.map(x => getItems(x.url)))]);
}).then(([itemList, items]) => {
items.forEach(($, i) => {
itemList[i].total_page = $('.pagination .page').toArray().length;
});
return itemList;
}).then(console.log);
I think that will get you there (i.e. with the side-effected itemList). Note that this will fail if any of your getItems calls fails.
This is actually a common problem with Promises: you want to use the results asynchronously (as if you had a choice) but combine that with the original results. So here I've used Promise.all to resolve an array of Promises of the getItems calls which turns that into a single Promise that awaits all of the results. Then I put that in an array with the itemList and then call Promise.all on that and return that Promise. Then next .then in the chain gets an array with two arrays inside: the original itemList and the array of the now-resolved calls to getItems. At this point the forEach executes the side effects, and we just return the now updated itemList.

Categories

Resources