It's a shame that arrays in JavaScript don't have built-in support for asynchronous operations; for example, there's no mapAsync() method that takes an asynchronous callback and produces a Promise of an array of callback results. You could build such functionality yourself, but it's probably not worth it here.
Additionally, the async/await syntax in JavaScript doesn't support awaiting arrays-of-promises directly without using something like Promise.all() as you've seen. There's at least one proposal to support something like await.all on an array of promises as syntactic sugar for await Promise.all(...), but it's not part of the language as of yet.
So for now, at least, using async/await is more conducive to imperative-style programming (e.g., for loops) than it is to functional-style programming (e.g., Array.prototype.map()). For example, if we switch to an imperative style, things probably look less "clunky":
async function getPartners(): Promise<Partner[]> {
const partners: Partner[] = [];
const ids = await getIds();
for (let id of ids) {
const token = await getToken(id);
const partner = await getPartner(token);
partners.push(partner);
}
return partners;
}
Conversely, functional-style programming tends to interact better with Promise methods and interfaces, and doesn't benefit much from async and await. For example:
function getPartners(): Promise<Partner[]> {
return getIds()
.then(ids => Promise.all(ids.map(i => getToken(i))))
.then(tokens => Promise.all(tokens.map(token => getPartner(token))));
}
or possibly
function getPartners(): Promise<Partner[]> {
return getIds().then(ids => Promise.all(
ids.map(i => getToken(i).then(token => getPartner(token))))
);
}
Either way should work. Or your "clunky" method is also fine, if you don't mind a mix of styles.
And please note that the refactorings above could have different behavior when it comes to performance; the imperative version waits for each asynchronous call to complete before making the next one, while the ones with Promise.all() are, at least theoretically, concurrent. In practice this might or might not matter depending on how long the calls take and whether your environment can actually spray requests concurrently instead of queuing them up somewhere.
Playground link to code