typescript несколько последовательных обещаний

#typescript #promise

#машинопись #обещание

Вопрос:

У меня есть функция, которая работает. Идея заключается в том, что идентификаторы получаются из etcd, затем они используются для получения токена, а затем токены, используемые для извлечения данных.

Затем возвращается массив

   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
  } 
 

Моя проблема в том, что это выглядит немного неуклюже. Я пытался вложить обещания, но обычно они возвращаются раньше, прежде чем выполнять выборку токена и данных. Действительно ли это лучший способ?

Ответ №1:

Жаль, что массивы в JavaScript не имеют встроенной поддержки асинхронных операций; например, нет mapAsync() метода, который принимает асинхронный обратный вызов и выдает Promise массив результатов обратного вызова. Вы могли бы создать такую функциональность самостоятельно, но, вероятно, здесь это того не стоит.

Кроме того, синтаксис async / await в JavaScript не поддерживает ожидающие массивы обещаний напрямую, не используя что-то вроде Promise.all() того, что вы видели. Есть по крайней мере одно предложение для поддержки чего-то вроде await.all массива обещаний в качестве синтаксического сахара await Promise.all(...) , но пока это не является частью языка.

Так что, по крайней мере, на данный момент использование async / await более благоприятно для программирования в императивном стиле (например, for циклы), чем для программирования в функциональном стиле (например, Array.prototype.map() ). Например, если мы переключимся на императивный стиль, все, вероятно, будет выглядеть менее «неуклюже»:

 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;
}
 

И наоборот, программирование в функциональном стиле, как правило, лучше взаимодействует с Promise методами и интерфейсами и не извлекает большой пользы из async и await . Например:

 function getPartners(): Promise<Partner[]> {
  return getIds()
    .then(ids => Promise.all(ids.map(i => getToken(i))))
    .then(tokens => Promise.all(tokens.map(token => getPartner(token))));
}
 

или, возможно

 function getPartners(): Promise<Partner[]> {
  return getIds().then(ids => Promise.all(
    ids.map(i => getToken(i).then(token => getPartner(token))))
  );
}
 

В любом случае должно сработать. Или ваш «неуклюжий» метод тоже подойдет, если вы не возражаете против сочетания стилей.

И, пожалуйста, обратите внимание, что приведенные выше рефакторинги могут иметь разное поведение, когда дело доходит до производительности; императивная версия ожидает завершения каждого асинхронного вызова перед выполнением следующего, в то время как те, с Promise.all() которыми, по крайней мере теоретически, параллельны. На практике это может иметь или не иметь значения в зависимости от того, сколько времени занимают вызовы и может ли ваша среда фактически распылять запросы одновременно, а не ставить их где-то в очередь.

Игровая площадка ссылка на код

Комментарии:

1. Спасибо — я выбрал последний функциональный вариант. На данный момент у меня есть только небольшой набор данных, но я полагаю, что если он будет расти, может возникнуть шторм запросов