I have one promise that relies on the return value of another promise. I'm successfully passing that value to the second promise, but there's additional data that's created in the first promise that I'd like to get into the second promise somehow.
Here's my code:
const getAlbumInfo = async (album, artist) => {
let hostname = 'http://www.musicbrainz.org';
let path = `/ws/2/release-group/?query=release:${album}%20AND%20artist:${artist}&fmt=json`;
let res = await fetch(hostname + path);
return res.json();
};
const getAlbumArt = async (albumInfo) => {
let res = await fetch(`${albumInfo.url}`);
return res.json();
};
let artist = '', album = '';
getAlbumInfo(album, artist)
.then((res) => {
const metadata = {
album: res['album'],
artist: res['artist'],
url: res['url']
};
return getAlbumArt(metadata);
})
.then((res) => {
console.log(res); // prints result from getAlbumArt
// I also need the contents of metadata here
})
.catch(e => { console.error(e); });
So I'm getting album metadata in the first promise, which includes a URL, and then I'm running a fetch on that URL, which returns a second promise. The problem is that I need access to the album metadata in the second promise.
Since I'm returning/passing the metadata into the second promise, I tried using Object.assign() inside getAlbumArt() to combine the metadata and the fetch result, but that didn't seem to work.
There are multiple ways of doing this, you could use await:
const getAlbumInfo = async(album, artist) => {
let hostname = 'http://www.musicbrainz.org';
let path = `/ws/2/release-group/?query=release:${album}%20AND%20artist:${artist}&fmt=json`;
let res = await fetch(hostname + path);
return res.json();
};
const getAlbumArt = async(albumInfo) => {
let res = await fetch(`${albumInfo.url}`);
return res.json();
};
let artist = '',
album = '';
const getAll = async() => {
const res1 = await getAlbumInfo(album, artist);
const metadata = {
album: res['album'],
artist: res['artist'],
url: res['url']
};
const res2 = await getAlbumArt(metadata);
// here you have access to both responses (res1 and res2) and the created metadata object.
}
If you use this, you should wrap the calls in try..catch...
Another option will be to pass the metadata from the second promise along with the response:
const getAlbumInfo = async(album, artist) => {
let hostname = 'http://www.musicbrainz.org';
let path = `/ws/2/release-group/?query=release:${album}%20AND%20artist:${artist}&fmt=json`;
let res = await fetch(hostname + path);
return res.json();
};
const getAlbumArt = async(albumInfo) => {
let res = await fetch(`${albumInfo.url}`);
res = await res.json();
return {
res,
albumInfo
};
};
let artist = '',
album = '';
getAlbumInfo(album, artist)
.then((res) => {
const metadata = {
album: res['album'],
artist: res['artist'],
url: res['url']
};
return getAlbumArt(metadata);
})
.then((o) => {
console.log(o.res, o.albumInfo);
})
.catch(e => {
console.error(e);
});
And a third option will be to resolve the second promise inside the first promise's callback function:
getAlbumInfo(album, artist)
.then((res) => {
const metadata = {
album: res['album'],
artist: res['artist'],
url: res['url']
};
getAlbumArt(metadata)
.then(res2 => {
// here you can access res, res2 and metadata
})
.catch(..);
})
.catch(..);
Since you could use async/await, we could solve the problem using this too. It's better to consistent using async/await instead of mix it with Promise.
To do that, we need IIFE
(async () => { // define IIFE
try { // try-catch to ensure error is catched
const res = await getAlbumInfo(album, artist);
const metadata = {
album: res["album"],
artist: res["artist"],
url: res["url"]
};
const res2 = await getAlbumArt(metadata);
console.log(res2);
console.log(metadata);
} catch (error) {
console.error(error);
}
})();
Hope it helps
await for the second promise and then return both to the next .then block. Use deconstruct for a simpler use case:
getAlbumInfo(album, artist)
.then(async ({album, artist, url}) => {
const metadata = {album, artist, url};
const arts = await getAlbumArt(metadata);
return {arts, metadata};
})
.then(({arts, metadata}) => {
// You have both here
})
.catch(console.log);
Alternatively, this entire action can be inside an async function:
const getAlbum = async (album, artist) => {
const info = await getAlbumInfo(album, artist);
const metadata = {
album: info.album,
artist: info.artist,
url: info.url,
};
const arts = await getAlbumArt(metadata);
// everything is available;
};
You can use then in the inner promise and return any format of data u want frm there.
getAlbumInfo(album, artist)
.then((res) => {
const metadata = {
album: res['album'],
artist: res['artist'],
url: res['url']
};
return getAlbumArt(metadata).then(responseArt => {
return {
innerPromise: responseArt,
outerPromise: metadata
};
});
})
.then((res) => {
console.log(res); // Now u have both here
// I also need the contents of metadata here
})
.catch(e => { console.error(e); });
Related
This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 3 months ago.
I want to make multiple calls for which I have to wait for the answer, and afterwards I want to group all responses in an array. I've not succeeded to do this.
The res constant in code below still retains the array of promises, not their results. I have no idea what else to try. No other stackoverflow answers have been helpful.
What I've tried:
const getProjectData = async (projectID) => await callAPI(`/projects/${projectID}`);
const solve = async () => {
const projects = [];
currentUserProjectIDs.forEach((project) => {
projects.push(getProjectData(project));
});
console.log("projects", projects);
const res = await Promise.all(projects);
console.log("solve res", res);
return res;
};
const res = await solve();
console.log("res", res);
Below is the result of the last console log:
res [
Response {
size: 0,
timeout: 0,
[Symbol(Body internals)]: { body: [PassThrough], disturbed: false, error: null },
[Symbol(Response internals)]: {
url: 'http://localhost:4000/projects/1',
status: 200,
statusText: 'OK',
headers: [Headers],
counter: 0
}
},
Response {
size: 0,
timeout: 0,
[Symbol(Body internals)]: { body: [PassThrough], disturbed: false, error: null },
[Symbol(Response internals)]: {
url: 'http://localhost:4000/projects/4',
status: 200,
statusText: 'OK',
headers: [Headers],
counter: 0
}
}
]
callAPI function:
export const callAPI = async (path, body, method = "GET") => {
const config = {
method: method,
headers: {
"Content-Type": "application/json",
},
};
if (body) {
config.body = JSON.stringify(body);
}
const URL = `${process.env.HOST}${path}`;
return await fetch(URL, config);
};
EDIT:
I have tried another way, but still unsuccessful. In the code below, the console.log inside the second .then() logs the correct data, but the returned prj is still an empty array...
const solve = async () => {
const projects = [];
currentUserProjectIDs.map((p) => {
callAPI(`/projects/${p}`)
.then((r) => {
return r.json();
})
.then((a) => {
console.log("a", a);
projects.push(a);
return a;
});
});
return projects;
};
const prj = await solve();
console.log("prj", prj);
Your edit is almost correct, but you have to use the array returned from .map and include the Promise.all from your original post:
const solve = async () => {
const projects = currentUserProjectIDs.map((p) => {
return callAPI(`/projects/${p}`)
.then((r) => {
return r.json();
});
});
return Promise.all(projects);
};
const prj = await solve();
console.log("prj", prj);
Pushing to a separate array won’t work because the code in .then is executed asynchronously.
You will probably also want to handle errors with .catch, I assume you omitted that for brevity.
This is a bit cleaner solution based on your first attempt:
export const callAPI = async (path, body, method = 'GET') => {
const config = {
method: method,
headers: {
'Content-Type': 'application/json'
}
}
if (body) {
config.body = JSON.stringify(body)
}
const URL = `${process.env.HOST}${path}`
const res = await fetch(URL, config)
if (!res.ok) throw new Error('Error fetching data')
return res.json()
}
const getProjectData = (projectID) => callAPI(`/projects/${projectID}`)
const solve = () => {
const projects = currentUserProjectIDs.map((project) => {
return getProjectData(project)
})
return Promise.all(projects)
}
solve()
.then((data) => console.log(data))
.catch((err) => console.error(err))
The problem was (before your first edit) that you never called the json() method of the response, that's why you were getting an array of Response objects.
Let me know if that helps.
You can do so:
const getProjectData = async (projectID) => await callAPI(`/projects/${projectID}`);
const solve = async () => {
const res = await Promise.all(
currentUserProjectIDs
.map(id => getProjectData(id)
.then(res => res.json())
);
return res;
};
const res = await solve();
In the following code, I used Promise.all to enable async called when calling map, but in the then portion, i need another call to await, the syntax does not allow me to do that, what's the way to enable await under then branch? Alternatively, is there more elegant way to achieve the same purpose of my code without introducing so many different constructs like Promise.all?
const handleSend = async ({ text, attachments }) => {
const attachmentsArr = null;
Promise.all(attachments.map(async a => {//<--use Promise all for async within map
const isImage = a.type.startsWith('image/');
let response = null;
if (isImage) { response = await channel.sendImage(a); } else { response = await channel.sendFile(a); }
return {
name: a.name,
path: response.file,
size: a.size,
isImage
};
})).then(attachmentsArr => {
await channel.sendMessage({ //<--can't use await here
text,
workspaceId: '1234567',
attachments: attachmentsArr
});
}
);
};
I would write it like this
const handleSend = async ({ text, attachments }) => {
const promisesArray = attachments.map(async a => {
const isImage = a.type.startsWith('image/');
let response = null;
if (isImage) {
response = await channel.sendImage(a);
} else {
response = await channel.sendFile(a);
}
return {
name: a.name,
path: response.file,
size: a.size,
isImage
};
});
const attachmentsArr = await Promise.all(promisesArray);
await channel.sendMessage({
text,
workspaceId: '1234567',
attachments: attachmentsArr
});
};
But, you should check attachmentsArr and see how to know when is empty to send null.
Mixing async/await with then can be confusing. You can instead write it all in async/await syntax like Braian's answer, or in then syntax like this:
const handleSend = ({ text, attachments }) => {
Promise.all(attachments.map(async a => {
const isImage = a.type.startsWith('image/');
let response = null;
if (isImage) { response = await channel.sendImage(a); } else { response = await channel.sendFile(a); }
return {
name: a.name,
path: response.file,
size: a.size,
isImage
};
})).then(attachmentsArr => {
return channel.sendMessage({ // <--- Return a promise
text,
workspaceId: '1234567',
attachments: attachmentsArr
});
}).then((result) => { // <--- Operate on the resolved value
// Do something with result
});
};
I have a function that fetches data from an API, and the function works correctly as intended:
const getStockData = async (stock) => {
try {
const response = await axios.get(`${BASE_URL}${stock}${KEY_URL}`);
console.log(response);
return response;
} catch (error) {
console.error('Error', error.message);
}
};
And I have another function that gets data from my firebase which then passes in the .ticker into the function above however when I log the response from the promise the data is returned null
Is there a reason why its not working as intended?
const getMyStocks = async () => {
let promises = [];
let tempData = [];
const querySnapshot = await getDocs(collection(db, 'myStocks'));
querySnapshot.forEach((doc) => {
console.log(doc.data().ticker);
promises.push(
getStockData(doc.data().ticker).then((res) => {
console.log(res);
tempData = {
id: doc.id,
data: doc.data(),
info: res.data,
};
})
);
getMyStocks must return the resolution of the promises it creates...
// to reduce nested promises, this takes an FB doc and adds the getStockData query to it
const getFBAndTickerData = async doc => {
return getStockData(doc.data().ticker).then(res => {
console.log(res);
return {
id: doc.id,
data: doc.data(),
info: res.data,
};
});
}
const getMyStocks = async () => {
const querySnapshot = await getDocs(collection(db, 'myStocks'));
let promises = querySnapshot.docs.map(doc => {
console.log(doc.data().ticker);
return getFBAndTickerData(doc);
});
return Promise.all(promises);
}
I'm fetching data from API and i would like to add property to my object.
I'm currently adding image property but i need to add this one layer deeper inside object.
Could you give me a hint how to achieve that?
I have stuck on this moment:
My code:
const Review = () => {
const url = "https://jsonplaceholder.typicode.com/users";
const [people, setPeople] = useState(null);
const fetchPeople = async () => {
try {
const response = await fetch(url);
const data = await response.json();
let test = Object.entries(data).map((people) => ({
...people,
image: "image url goes here",
}));
setPeople(test);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
fetchPeople();
console.log(people);
}, []);
Object.entries returns an array [index, value] . You just need to add image property to value which is an object.
fetch("https://jsonplaceholder.typicode.com/users").then(r => r.json()).then(data => {
const newData = Object.entries(data).map(people => {
people[1].image = "some image here"
return people;
});
console.log(newData);
});
Using object.entries was bad idea :D
Solution for my question is:
const fetchPeople = async () => {
try {
const response = await fetch(url);
const data = await response.json();
const peopleWithImages = data.map((person, index) => {
return {
...person,
image: `https://robohash.org/?set=set${index + 1}`,
};
});
setPeople(peopleWithImages);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
fetchPeople();
}, []);
I have the following snippet of code
export const fetchPosts = () => async dispatch => {
const res = await axios.get(`${url}/posts`, { headers: { ...headers } });
console.log(res.data);
let posts = res.data.map(p => (p.comments = fetchComments(p.id)));
console.log(posts);
dispatch({ type: FETCH_POSTS, payload: res.data });
};
export const fetchComments = id => async dispatch => {
console.log(id)
const res = await axios.get(`${url}/posts/${id}/comments'`, {
headers: { ...headers }
});
console.log("id", id);
return res.data;
};
when i console log the posts, i get 2 functions returned. what is the proper way in which i should call the fetch comments for this function to return me the desired value?
Add this:
const postsResult = await Promise.all(posts)