Reputation: 9075
I have a function - which works. The idea is that the ids are got from etcd, then those are used to get a token and then the tokens used to fetch the data.
Then an array is returned
public async getPartners(): Promise<Partner[]> {
const ids = await this.getPartnerIds() // etcd
const tokens = await Promise.all(ids.map(i => Config.getToken(i))) // JWT
const partners = await Promise.all(tokens.map(token => this.get<Partner>('url', token as string))) // data
return partners
}
My problem is that it looks a bit clunky. I've tried nesting the promises but it usually returns early before doing the token fetching and data. Is this really the best way?
Upvotes: 1
Views: 1113
Reputation: 327994
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.
Upvotes: 1