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
});
};
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();
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 would like to return an object from the the async / await function A to pass it to another function.
Currently what I get as a result is Promise{ <pending> }' or undefined.
function A:
const parseRss = data => data.forEach(rssLink => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
}
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return (async () => {
return await getFeed();
})();
});
Function B:
try {
const data = await getDataWithRss();
const emailData = await parseRss([{rss:'http://reddit.com/.rss'}]); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
How do I return emailContent from function A to use it in function B?
Thanks!
Since you made getFeed as async, no need another async. You are looping through, so return an array of promises. Once the you call use Promise.all to resolve. Since there could be multiple urls to fetch.
const parseRss = (data) =>
data.map((rssLink) => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: [],
},
};
feed.items.forEach((item) => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`);
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return getFeed();
});
try {
const data = await getDataWithRss();
const emailData = await Promise.all(parseRss([{rss:'http://reddit.com/.rss'}])); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
await will not work inside a forEach loop. Use a for...in loop instead.
actually, getFeed() is not necessary inside inner scope, you can use async in map callback:
const parseRss = data => data.map(async rssLink => {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
};
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
});
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.