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();
Related
I have an array of objects (mergedArray) that I need to iterate through, call an api for each object, then set that response as a new value. Every time I do this the array gets returned with industry: Promise { <pending> }
let responseArray = await Promise.all( mergedArray.map(i =>{
return (
{...i, industry: atomTest()}
)
}))
console.log(responseArray)
Using this function to fetch the data..
const atomTest = async () => {
await fetch('https://*********',
{
method: 'POST',
headers: {accept: 'application/json', 'content-type': 'application/json'},
body: JSON.stringify({
asset: {identifier: 'ticker', value: 'NKE', assetType: 'equity', market: 'USA'}
})
})
.then(response => response.json())
.then((response )=> {
console.log(response.lists[0].name)
return(response.lists[0].name)
.catch(err => console.error(err));
}
Replacing 'atomTest()' with a function that just returns a string gives the correct result, how would I do this with an async function? I can't seem to figure out how to let the function wait for the fetch result before moving on.
Any help would be massively appreciated :)
You need to await for the API call to complete in the map callback:
async function callAPI(n) {
return fetch('https://jsonplaceholder.typicode.com/todos/' + n)
.then(response => response.json())
}
mergedArray = [1, 2, 3]
async function main() {
let responseArray = await Promise.all(mergedArray.map(async i => {
return {i, industry: await callAPI(i)}
}))
console.log(responseArray)
}
main()
So i have two functions that fetch data from the API. One of them updates the todo completion, and the other one fetches the list of todos. Currently the UpdateAndFetch() function lags behind, and sometimes returns the not updated list.
How can i fix this?
Functions that make API calls
let base = import.meta.env.VITE_API_BASE
// fetch the todos that belong to groupId
export async function TESTFetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
}
//
export async function TESTupdateCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.log('error', error))
}
Function inside the Component
async function UpdateAndFetch(todo, groupId) {
const updateTodoCompletion = await TESTupdateCompletion(todo)
const updateTodoList = await TESTFetchTodosFromGroup(groupId, todos, groupName)
}
You always have to return a fetch function otherwise it will not pass the value to the next async call.
And in order to have the ability to execute the function one by one you can do this where you call your functions.
async function UpdateAndFetch(todo, groupId) {
Promise.resolve(() => updateTodoCompletiong(todo)).then(response => fetchTodosFromGroup(groupId,todos,groupName))
}
after that, you can catch errors.
You can read the documentation here it is really very helpful what javascript provides. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
also if any questions leave a comment.
Nevermind, found how to fix this.
Wrap the fetch functions to be a return value
// fetch the todos that belong to groupId
export async function fetchTodosFromGroup(groupId, todos, groupName) {
let url = `${base}/todos/group/${groupId}`
// you need to wrap the fetch into return so that the awaits would work !
return fetch(url).then(async (response) => {
const json = await response.json()
todos.value = json.data
groupName.value = json.message
if (!response.ok) {
return Promise.reject(error)
}
})
// Promise.resolve('done')
}
//
export async function updateTodoCompletion(todo) {
//
let url = `${base}/todos/completion/${todo.todoId}`
var requestOptions = {
method: 'PATCH',
redirect: 'follow',
}
// you need to wrap the fetch into return so that the awaits would work !
return (
fetch(url, requestOptions)
.then((response) => response.json())
.then((result) => console.log(result))
// .then(() => TESTFetchTodosFromGroup())
.catch((error) => console.log('error', error))
)
}
Refactor the function that executes the functions
// delete the todo and fetch the list
async function UpdateAndFetch(todo, groupId) {
await updateTodoCompletion(todo)
await fetchTodosFromGroup(groupId, todos, groupName)
}
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); });
I have an async function that returns a value
async getUsername(env:string,skuName: string,token:string): Promise<any> {
let value = await this.getDeviceName(env,skuName,token);
return value;
};
in another function, I am calling this get username function like this
let productN;
const prod = this.getUsername(env,skuName,token).then((resp) => {
productN= resp;
this.wait(300);
});
the variable productN is working, as i am able to see the response in my log, but when i try to use productN outside of this block, i am running into undefined.
promotion = {
name: productN,
startDate: start,
endDate: end
};
I am trying to set name to productN, and i just can not get it to work.
Can anyone explain to me what i am doing wrong please? Thanks
You can either assign to the promotion when you receive the response -
const prod = this.getUsername(env,skuName,token).then((resp) => {
productN = resp;
promotion.name = resp
this.wait(300);
});
Since your getUsername is asynchronous, you can wait for the response using await and then assign to promotion object.
(async() => {
const resp = await this.getUsername(env,skuName,token);
promotion = {
name: resp,
startDate: start,
endDate: end
}
})();
--- Edit ---
const input = [
{
env: 'lorem',
skuname: 'skuname1',
token: 'dummy'
},
{
env: 'lorem',
skuname: 'skuname2',
token: 'dummy'
}
];
const getUserName = (username) => {
return new Promise(resolve => {
setTimeout(()=>resolve(username), 2000);
});
};
(async () => {
const resp = await Promise.all(input.map(({skuname}) => getUserName(skuname)));
console.log(resp);
})();
// Or
Promise.all(input.map(({skuname}) => getUserName(skuname)))
.then(resp => {
console.log(resp);
});
try
const productN = await this.getUsername(env,skuName,token);
console.log(productN);
Can you try doing this? Return your getDeviceName function in getUsername;
getUsername(env:string, skuName: string, token:string): Promise<any> {
return this.getDeviceName(env, skuName, token);
};
const productN = await this.getUsername(env, skuName, token);
console.log(productN);
I don't know why you use getUsername. since you can get the value in productN directly from getDeviceName.
like
const productN = await this.getDeviceName(env, skuName, token);
If you want to do any other things inside getUsername. you can make it return a promise.
getUsername(env:string, skuName: string, token:string): Promise<any> {
return New Promise(async (resolve,reject)=>{
try {
let value = await this.getDeviceName(env,skuName,token);
...
//all other things you want to do here
...
resolve(value);
} catch (error) {
reject(error)
}
}
};
const productN = await this.getUsername(env, skuName, token);
console.log(productN);
nb: I used try catch block for error handling, Ignore it if you don't want to.
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)