Not receiving the correct data from Promises? - javascript

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);
}

Related

firebase storage always return Promise

I have an application on vue 3. I need to get a link to a document from the repository. At the moment, I always get a promise, which is how it should be. But I should get a link to the document, but it doesn't. Why is this happening?
async FetchData({ state, commit }, to) {
try {
commit("setLoading", true);
const q = query(collection(db, to));
await onSnapshot(q, (querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
let films = async (to) => {
const starsRef = ref(storage, `images/${doc.id}/poster.png`);
return await getDownloadURL(starsRef);
};
// const poster=`gs://cell-11ef4.appspot.com/images/${doc.id}/poster.png`
let item = {
id: doc.id,
name: doc.data().name,
slug: doc.data().slug,
country: doc.data().country,
duration: doc.data().duration,
year: doc.data().year,
video: doc.data().video,
genres: doc.data().genres,
actors: doc.data().actors,
poster: to === "films" ? films() : null,
// BipPoster: url,
};
data.push(item);
// Get the download URL
});
commit("setData", { data, to });
});
} catch (err) {
console.log(err);
} finally {
commit("setLoading", false);
}
}
let url = async () => {
let url;
const starsRef = ref(storage, `images/${doc.id}/poster.png`);
await getDownloadURL(starsRef).then((p) => {
url = p;
});
return url;
};
What i get
You should use Promise.all() as follows:
const promises = [];
querySnapshot.forEach((doc) => {
const starsRef = ref(storage, `images/${doc.id}/poster.png`);
promises.push(getDownloadURL(starsRef));
});
const urlsArray = await Promise.all(promises);
Map the documents collection to an array of promises that resolve with the full item data, including download URL and await them all with Promise.all().
You're also registering a real-time updates listener which seems counter to what you seem to want FetchData to do. I would suggest you want to use getDocs() instead of onSnapshot()
async FetchData({ state, commit }, to) {
try {
commit("setLoading", true);
// `onSnapshot` registers a real-time updates listener,
// use `getDocs` to retrieve documents
const { docs } = await getDocs(query(collection(db, to)));
// Map over the `docs` array and return fully hydrated objects
const data = await Promise.all(
docs.map(async (doc) => ({
...doc.data(),
id: doc.id,
poster:
to === "films"
? await getDownloadURL(ref(storage, `images/${doc.id}/poster.png`))
: null,
}))
);
commit("setData", { data, to });
} catch (err) {
console.error(err);
} finally {
commit("setLoading", false);
}
},
If you did want to register a real-time updates listener, do so in an effect hook where you can also remove the listener in a cleanup
useEffect(() => {
// Return the unsub function as cleanup
return onSnapshot(query(collection(db, to)), async ({ docs }) => {
try {
const data = await Promise.all(
docs.map(async (doc) => ({
...doc.data(),
id: doc.id,
poster:
to === "films"
? await getDownloadURL(
ref(storage, `images/${doc.id}/poster.png`)
)
: null,
}))
);
commit("setData", { data, to });
} catch (err) {
console.error(err);
}
});
}, [to]);
async FetchData({ state, commit }, to) {
try {
const q = query(collection(db, to));
await onSnapshot(q, (querySnapshot) => {
const allPromises = querySnapshot.docs.map(async (doc) => {
let item = {
id: doc.id,
name: doc.data().name,
slug: doc.data().slug,
country: doc.data().country,
duration: doc.data().duration,
year: doc.data().year,
video: doc.data().video,
genres: doc.data().genres,
actors: doc.data().actors
};
if (to === "films") {
const starsRef = ref(storage, `images/${doc.id}/poster.png`);
item.poster = await getDownloadURL(starsRef);
}
return item;
});
Promise.all(allPromises)
.then((data) => commit("setData", { data, to }))
.catch(console.error);
});
} catch (err) {
console.error(err);
}
}

Is there a way to make an api call within a map of another api call?

I know the title is quite confusing, I wasn't sure how to word it better. What I am trying to do is to fetch some items, map through those items to display them, but the problem is that one of those items has a value of what needs to be another api call to access it.
This is what I'm trying to do:
First of all I am storing an empty state, which later on becomes the data of the fetched items:
const [data, setData] = useState([]);
I'm using axios to fetch and store the data:
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then((response) => {
console.log(response.data.results);
const newData = response.data.results.map((item) => ({
name: item.name,
homeworld: () => {
axios.get(item.homeworld).then((response) => {
response.data.results;
});
},
}));
setData(newData);
})
.catch((error) => {
console.log("error", error);
});
};
It works with the name because it's a simple value. However, the homeworld includes a link that needs to be called once again in order to access it, instead of being a simple value like the name in this case. How can I call it and access what values are held within that link, and display them instead of just displaying the url?
I hope this can help you:
const [data,setData] = useState([])
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then(response => {
console.log(response.data.results);
const { results } = response.data;
for (const item of results) {
axios.get(item.homeworld).then(({data}) => {
setData([...data,{ name: item.name, homeworld: data.results }]);
});
}
})
.catch(error => {
console.log("error", error);
});
};
or with fetch:
const [data,setData] = useState([])
fetch("https://swapi.dev/api/people").then(re=>re.json())
.then(response => {
const newData = []
const { results } = response;
const newData = [];
for (const item of results) {
fetch(item.homeworld).then(re => re.json()).then((data) => {
newData.push({ name: item.name, homeworld: data });
});
}
console.log(newData)
setData(newData)
})
.catch(error => {
console.log("error", error);
});
Use Promise.all()
You can use Promise.all() method to get all the information you need by creating an array of promises by mapping the response.results array with an async function.
This is the code example
const fetchItems = async () => {
const req = await axios.get("https://swapi.dev/api/people");
const response = await req.data;
const allDataPromises = response.results.map(async (item) => {
const itemReq = await axios.get(item.homeworld);
const itemResponse = await itemReq.data;
return {
name: item.name,
homeworld: itemResponse,
};
});
const allData = await Promise.all(allDataPromises);
};
For further information about Promise.all()

How can I get additional data from one promise to another?

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); });

Multiple API calls with Promise.all and dispatch an action

I want to call multiple API's and store each response data in an object then I want to dispatch this response object but I'm getting undefined.
Below is the code I tried. May I know where I'm doing wrong?
/* COMPONENT.JSX */
componentDidMount() {
callApis(this.props.products, this.props.profileId);
}
/* API.JS */
const getContactDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/contact`));
const getProductDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/product`));
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
products.forEach((product) => {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
Promise.all(apis)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
});
dispatch({ type: FETCH_API_DATA, payload: payload });
}
I expect the dispatch will be called after all API's were resolved, get parsed, and map into the payload object then it should dispatch.
Array.map returns a new Array, which you are discarding
you're calling dispatch before any of the asynchronous code has run
A few minor changes are required
/* API.JS */
const getContactDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/contact`);
const getProductDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/product`);
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
// *** 1
const outerPromises = products.map((product) => {
const apis = [getContactDetails, getProductDetails];
// *** 2
const promises = apis.map(api => api(http, profileId));
// *** 3
return Promise.all(promises)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}));
// *** 4
Promise.all(outerPromises)
.then(() => dispatch({
type: FETCH_API_DATA,
payload: payload
})
)
.catch(err => console.log(err));
}
rather than procucts.forEach, use products.map
capture the promises in apis.map to use in Promise.all
return Promise.all so the outer Promises can be waited for
Promise.all on the outer promises, to wait for everything to complete.
const callApis = (products, profileId) => async (dispatch) => { // use async function
const payload = new Map();
for (const product of products) {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
await Promise.all(apis) // await all promise done
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}
dispatch({ type: FETCH_API_DATA, payload: payload }); // dispatch will be executed when all promise done
}

how to properly use the async and await keywords within a map

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)

Categories

Resources