KeepCalmAndCarryOn
KeepCalmAndCarryOn

Reputation: 9075

typescript multiple promises in sequence

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

Answers (1)

jcalz
jcalz

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.

Playground link to code

Upvotes: 1

Related Questions