I'm having difficulty understanding why I still have pending promises after awaiting Promise.all().
In the example below, I'm creating an array of promises by calling an async function on each element of an array, using .map().
Now, why is the promise still showing as pending? The way I (mis)understand it right now:
then() fires once the promise from storeData() resolves
storeData()resolves once newDataArray is returned
newDataArray is returned once all promises inside the promisesArray are resolved or once the first one rejects.
storeData(OldDataArray).then(values => console.log(values))
// console shows:
// { id: 1, data: Promise { <pending> } },
// { id: 2, data: Promise { <pending> } }
const storeData = async (OldDataArray) => {
try {
const promisesArray = OldDataArray.map((item) => {
let newData = downloadMoreDetails(item.id, item.group); //async function, see below
return {
id: item.id,
data: newData,
};
});
const newDataArray = await Promise.all(promisesArray); // <-- I'm awaiting all promises to complete before assigning to newDataArray
return newDataArray;
} catch (error) {
console.log(error)
}
};
const downloadMoreDetails = async (id, group) => {
const response = await fetch(
`example.com/id/group.xml`
);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const str = await response.text();
const json = convert.xml2json(str, {
compact: true,
spaces: 2,
});
return json;
};
newData is a promise, but you're not awaiting the promise. Instead, you're awaiting an array of objects {id: item.id, data: newData } that has the promise inside it. Promise.all() doesn't look inside those objects to find the promise and wait for that promise. It just sees an array of plain objects which means it has nothing to do. You can fix that by doing this:
const storeData = async (OldDataArray) => {
try {
const promisesArray = OldDataArray.map(async (item) => {
let newData = await downloadMoreDetails(item.id, item.group); //async function, see below
return {
id: item.id,
data: newData,
};
});
return Promise.all(promisesArray);
} catch (error) {
// log and rethrow error so the caller gets the rejection
console.log(error);
throw error;
}
};
This changes the .map() callback to be async. That does two beneficial things. First, it means the resulting array from .map() will be an array of promises since the async callback always returns a promise. And, second, it allows you to use await inside the callback so you can populate your returned object with the actual data, not with a promise.
Then, the return from inside the async callback will cause that value to become the resolved value of the promise that the async function is returning.
Note, you could have also done it without adding the async/await like this:
const storeData = (OldDataArray) => {
const promisesArray = OldDataArray.map((item) => {
return downloadMoreDetails(item.id, item.group).then(newData => {
return {
id: item.id,
data: newData,
};
});
});
return Promise.all(promisesArray).catch(error => {
// log and rethrow error so the caller gets the rejection
console.log(error);
throw error;
});
};
In this version, you directly return a promise from the .map() callback and you make sure that promise resolves to your data object.
Related
I'm trying to promisify a function that uses https.get() to get data from an API, so that I can use async await. Here is the example below, it retrieves the data however if I do anything to access the object, such as theData[0] shown below, it will only return Promise { <pending> }
Why am I not able to get asynchronous operation I'm trying for below? Or is there a better way to do this? Basically I'm just trying to make an asynchronous operation with async await, and I need to use https (not fetch, axios, etc).
const myRequest = ((url) => {
return new Promise((resolve, reject) => {
https.get(url, res => {
let data = [];
res.on('data', chunk => {
data.push(chunk);
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}).on('error', err => {
console.log('Error: ', err.message);
});
});
});
async function getApi() {
// write your code here
let url = [url redacted]
await myRequest(url)
.then(data => {
console.log(data[0])
})
.catch(err => {
console.log(err)
});
}
Edit: refactored my helper function with the error handler:
}).on('error', err => {
reject(err)
});
However the promise is still not delivering properly. If I do a simple console.log of the data
async function getApi() {
let url = [url redacted]
const data = await myRequest(url);
console.log(data)
}
It will return Promise { <pending> } and then the JSON:
Promise { <pending> }
{
page: 1,
per_page: 500,
total: 1,
total_pages: 1,
data: [
{
date: '5-January-2000',
open: 5265.09,
high: 5464.35,
low: 5184.48,
close: 5357
}
]
}
I can access that object in the console.log, however, if i try to access anything in this object, it just returns Promise { <pending> }. Such as :
async function getApi() {
let url = [url redacted];
const data = await myRequest(url)
const {high, open} = data.data[0];
return `High: ${high} \n Open: ${open}`
}
First, you need to handle reject as well, or an error will cause a hang. Add reject(err) to your error handler to correctly bubble up the error.
Once you're sure the issue isn't an error, the await usage needs work. await is a tool that takes a promise, and then waits until it resolves to execute synchronously. If you then an awaited promise, you're not getting anything out of your await.
Rewrite the getApi function as:
async function getApi() {
let url = [url redacted]
const data = await myRequest(url);
console.log(data);
}
That will get and log the data, and if the promise is properly rejected, it will bubble that up as an exception.
I have an array of promises that looks like this:
const content = await getData() //gets data from API
const drinkURL = content.drinks.map( item => "lookup.php?i=" + item.idDrink) // gets element in the API end-point
const getInfo = async() => {
let allPromises = Promise.all(drinkURL.map(item => getData(item)))
allPromises.catch(e => console.log(e)) // Trying to catch any errors here, but can't.
return allPromises
}
I want to add a catch statement in case any of the promises can not resolve.
I have tried writing it inside the getInfo function, and after the .then() inside the next snippet.
const view = `
<div class="Drink-card">
Home
${await getInfo()
.then(ele => ele.map(item => item.drinks.map( drink => `
<article class="Drink-main">
<img src="${drink.strDrinkThumb}" alt="${drink.strDrink}">
<h2>${drink.strDrink}</h2>
</article>
`)).join("")).catch(e => console.log(e))//Does not work in here either}
I have tried a bunch of ways but can't come up with a working result.
Thanks!
You should use await keyword (1) combines with catch inside of each promise (2) as the following simple sample :
Full code sample below:
function getData(object){
return new Promise(resolve => {
if(object.value === "3")
throw new Error("Whoops!")
else
resolve(object.value);
});
}
var drinkURLs = [{ value: "1" }, { value: "2" }, { value: "3" }];
var getInfo = async() => {
var promises = drinkURLs.map(item => getData(item));
var results = await Promise.all(promises.map(p => p.catch(e => e)));
var inValidResults = results.filter(result => (result instanceof Error));
var validResults = results.filter(result => !(result instanceof Error));
console.log({inValidResults, validResults});
return validResults;
}
await getInfo();
You can use Promise.allSettled() function.
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise while Promise.all rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.
I'm having an issue with Promise.all in React. I'm creating promises for my axios request and at the end run a Promise.all to dispatch all the data.
Here is my method where I create the Promises:
function promiseLoad(nameID, uID, caption) {
return new Promise( () => {
axios.get(`${URL}/users/${uID}/name/${nameID}`)
.then(res => {
let obj;
if(res.data !== -1) {
obj = {
fullName: res.data,
caption: caption
};
}
return obj;
});
});
}
Then, in my export method, I run the Promise.all before dispatch it to the reducer.
export const loadUsers = (users, uID) => {
return dispatch => {
let promises = [];
imgs.forEach(element => {
promises.push(promiseLoad(element.id, uID, element.caption));
});
console.log('Promises');
console.log(promises);
Promise.all(promises)
.then(res => {
console.log('here');
console.log(res);
dispatch(getUsers(res));
});
}
}
The getUsers just a helper method to return the type/action
export const getUsers = (res) => {
return {
type: actionTypes.GET_USERS,
payload: res
}
}
When I run the code, I can see in the log:
Promises
Array(10) // array with 10 pending promises
The logs inside the .then() method of Promise.all, never runs.
axios.get returns a promise already, so you don't need to wrap it in the Promise constructor. Note that if you do construct a Promise, to avoid it never resolving, you must at least call resolve in the executor. In your case, promiseLoad was returning a Promise that never resolved, so you weren't seeing those logs.
function promiseLoad(nameID, uID, caption) {
return axios.get(`${URL}/users/${uID}/name/${nameID}`)
.then(res => {
let obj;
if(res.data !== -1) {
obj = {
fullName: res.data,
caption: caption
};
}
return obj;
});
}
I am trying to understand Promise.all here.
What I did here was to covert below code using Promise.all to achieve the same result.
I understand that Promise all combine the data1, data2.
My question here is that how does Promise.All work without resolve method?
Does Promise resolve those data within the method itself?
Please advise.
const readAllUsersChaining = () => {
return new Promise((resolve, reject) => {
let result = [];
getDataFromFilePromise(user1Path)
.then((data) => {
result.push(JSON.parse(data)); // what are you doing? he's gone mad...
return getDataFromFilePromise(user2Path);
})
.then((data) => {
result.push(JSON.parse(data));
result ? resolve(result) : reject(result);
});
});
};
const readAllUsers = () => {
const data1 = getDataFromFilePromise(user1Path);
const data2 = getDataFromFilePromise(user2Path);
console.log(data1, data2);
return Promise.all([data1, data2]).then((data) => {
return data.map((el) => JSON.parse(el));
});
};
My question here is that how does Promise.All work without resolve method?
Not quite sure what you mean. Promise.all simply creates a new promise internally that is resolved when all other promises are resolved.
Here is a simple implementation of Promise.all for the case that arguments are always promises:
function all(promises) {
if (promises.length === 0) {
return Promise.resolve([]);
}
return new Promise((resolve, reject) => {
const results = [];
let resolved = 0;
promises.forEach((promise, i) => {
promise.then(
result => {
results[i] = result;
resolved++;
if (resolved === promised.length) {
resolve(results);
}
},
error => reject(error)
);
});
}
Promise.all allows to wait until all promise passed as arguments to be fullfilled before the then method attach to be execute.
The real use case would be when you have to perform five call to an API, an you want to perform some treatement only when you have get the data from all the API call. you can rely on the Promise.all function which will wait all passed promised to be fullfilled for it to be fullfiled on it turn.
Bellow I provide an example of a Promise.all call. which has two Promises passed as argument. the first one has a timer which fullfilled it after 5 second and the second if fullfiled immediately but. the Promise.all will be fullfilled only when both of the promise passed as argument ar fullfilled
const firstPromise = new Promise((resolve, reject) => {
setTimeout(()=> {
return resolve({
name: "First Promise"
});
}, 5000);
});
const secondPromise = new Promise((resolve, reject) => {
return resolve({
name: "Second Promise"
});
})
const all = Promise.all([firstPromise, secondPromise]).then((response) => {
console.log(response);
});
I have the problem of having an undefined array which gets resolved after a for loop where it gets filled. It looks like the following:
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
}
// Array gets resolved
resolve(mailArray);
},
(error) => {
reject(error);
}
)
});
}
Both listMessages() and getMessage() returns a promise, so it is chained here. Any ideas why I am getting an undefined mailArray? My guess is that it is not filled yet when it gets resolved. Secondly I think this flow is not a good practice.
The Array is probably undefined because it is never defined; at least nowhere in your code. And your promise resolves before any iteration in your loop can resolve or better said, throw (trying to push to undefined).
Besides that. you can highly simplyfy your code by using Array#map and Promise.all.
And there's no point in catching an Error just to rethrow the very same error without doing anything else/with that error.
function getUnreadMails() {
//put this on a seperate line for readability
//converts a single messageId into a Promise of the result
const id2message = id => getMessage(oauth2Client, 'me', id);
return listMessages(oauth2Client)
//converts the resolved messageId into an Array of Promises
.then(messageIDs => messageIDs.map( id2message ))
//converts the Array of Promises into an Promise of an Array
//.then(Promise.all.bind(Promise));
.then(promises => Promise.all(promises));
//returns a Promise that resolves to that Array of values
}
or short:
function getUnreadMails() {
return listMessages(oauth2Client)
.then(messageIDs => Promise.all(messageIDs.map( id => getMessage(oauth2Client, 'me', id) )))
}
.then(Promise.all) won't work
I wanted to make the intermediate results more clear by seperating them into distinct steps/functions. But I typed too fast and didn't check it. Fixed the code.
In the short version, where does the mailArray then actually get filled/resolved
Promise.all() takes an an Array of promises and returns a single promise of the resolved values (or of the first rejection).
messageIDs.map(...) returns an Array and the surrounding Promise.all() "converts" that into a single Promise of the resolved values.
And since we return this Promise inside the Promise chain, the returned promise (listMessages(oauth2Client).then(...)) also resolves to this Array of values.
Just picking up on marvel308's answer, I think you need to create a new Promise that resolves when your other ones do. I haven't had a chance to test this, but I think this should work
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
var messages = [];
for(var i = 0; i < messageIDs.length; i++) {
messages.push(
getMessage(oauth2Client, 'me', messageIDs[i]).catch(reject)
);
}
Promise.all(messages).then(resolve);
},
(error) => {
reject(error);
}
)
});
}
This way, the resolve of your first promise gets called when all the messages have resolved
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
is an asynchronous call
resolve(mailArray);
won't wait for it to push data and would resolve the array before hand
to resole this you should use Promise.all()
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
mailArray.push(getMessage(oauth2Client, 'me', messageIDs[i]));
}
// Array gets resolved
return Promise.all(mailArray);
},
(error) => {
reject(error);
}
)
}
Since your getMessage function is async as well you need to wait until all your calls finish.
I would suggest using Promise.all
Here you can find more info: MDN Promise.all()
The code would look something like this:
messageIDs.map(...) returns an array of Promises
use Promise.all() to get an array with all the promises responses
resolve if values are correct reject otherwise
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
return Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
})
.then((messages) => resolve(messages))
.catch(error => reject(error))
});
}
One thing to keep in mind is that Promise.all() rejects if any of your promises failed
Hope this helps!
Explicit construction is an anti-pattern
I believe you can write that piece of code much shorter and, IMHO, cleaner
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return listMessages(oauth2Client)
.then((messageIDs) => Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
}