Async Await с четырьмя вложенными циклами

В настоящее время я пытаюсь вернуть массив объектов JSON, который требует от меня выполнения одной асинхронной функции, а затем четырех вложенных асинхронных функций карты, чтобы заполнить массив сущностей. По сути, у каждого пользователя есть массив заказов, у каждого заказа есть массив элементов, у каждого элемента есть массив опций, и у каждой опции есть массив значений. Я использую инфраструктуру loopback4 и поэтому не могу выполнить res.send, как только все будет заполнено. Кажется, что функция возвращается при первом ожидании, но любое ожидание после этого не ждет, а вместо этого выполняется до конца функции. Я пытался использовать Promises и .thens(), но не могу понять, как заполнить каждый полностью вложенный объект, а затем вернуть массив заполненных объектов. Я продолжаю получать пустой массив. Ниже только одно гнездо карт, но я не могу заставить его даже заполниться до первого гнезда и вернуть это, поэтому я решил не идти дальше. Это код:

async getUserOrders2(@param.path.number('id') id: number): Promise<any> {
      if ( !this.user) {
        throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
      }
      else if (this.user.id != id) {
        throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
      }
      else  {
        let restaurantId = this.user.restaurantId
        let orderFrameArray = new Array<OrderFrame>()
        return this.restaurantRepository.orders(restaurantId as string).find()
        .then(async orders => {
          orders.map(async (val, key)=> {
            let orderFrame = new OrderFrame(val)
            orderFrame.itemArray = await this.orderRepository.orderItems(val.id).find()
            orderFrameArray.push(orderFrame)
          })
          orderFrameArray = await Promise.all(orderFrameArray)
          return orderFrameArray
        })
      }
}

Функция возвращается до заполнения массива orderFrameArray. Мне нужно четыре вложенных цикла карт, и этот первый не работает, поэтому я не знаю, как сделать остальные. Любая помощь будет очень признательна.

Основываясь на решении @Tomalaks, я попробовал следующее, но он по-прежнему возвращает только массив верхнего уровня, и ничего не вложено:

    async getUserOrders2(@param.path.number('id') id: number): Promise<any> {
      if ( !this.user) {
        throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
      }
      else if (this.user.id != id) {
        throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
      }
      else  {
        let restaurantId = this.user.restaurantId
        let orderFrameArray = new Array<OrderFrame>()
        return this.restaurantRepository.orders(restaurantId as string).find()
          .then(orders => {Promise.all(orders.map(
          order => { 
          let orderFrame = new OrderFrame(order)
          orderFrame.itemArray = new Array<Item>()
          this.orderRepository.orderItems(order.id).find()
            .then(orderItems => Promise.all(orderItems.map(
            orderItem => {
            let itemFrame = new Item(orderItem)
            itemFrame.options = new Array<Option>()
            this.orderItemRepository.orderItemOptions(orderItem.id).find()
                .then(orderItemOptions => Promise.all(orderItemOptions.map(
                orderItemOption => { 
                let optionFrame = new Option(orderItemOption)
                optionFrame.values = new Array<Value>()
                this.orderItemOptionRepository.orderItemOptionValues(orderItemOption.id).find()
                    .then(orderItemOptionValues => Promise.all(orderItemOptionValues.map(
                    orderItemOptionValue => { 
                    let valueFrame = new Value(orderItemOptionValue)
                    optionFrame.values.push(valueFrame)})))
                itemFrame.options.push(optionFrame)})))
              orderFrame.itemArray.push(itemFrame)})))
            orderFrameArray.push(orderFrame)}))
          return orderFrameArray})
      }
    }

Прошу прощения за форматирование, я не был уверен, как лучше форматировать. Есть ли что-то еще, что я делаю неправильно?

Спасибо всем за ответ. Ответ, опубликованный @Tomalak, был правильным. Мне просто нужно было заключить всю функцию в скобки и поставить .then, чтобы вернуть заполненный объект, который я сделал


person Vikram Khemlani    schedule 30.09.2019    source источник
comment
Что возвращает this.restaurantRepository.orders()? Если функция возвращает фактические ордера, то .find() каждый из них ненужно заново. Если функция не возвращает фактические заказы, она должна иметь другое имя.   -  person Tomalak    schedule 30.09.2019
comment
Предполагается, что он возвращает все заказы в массиве. Так это неправильно тогда?   -  person Vikram Khemlani    schedule 30.09.2019
comment
... это не отвечает на мой вопрос.   -  person Tomalak    schedule 30.09.2019
comment
Ой, извини. restaurantRepository.orders принимает аргумент и возвращает объект репозитория, который я считаю. Вы используете .find для возврата массива всех заказов с внешним ключом, связанным с аргументом, который использовался для создания объекта.   -  person Vikram Khemlani    schedule 30.09.2019
comment
Предыстория вопроса заключалась в следующем: Нужно ли обращаться к базе данных снова или возвращаемые объекты уже являются объектами, которые вы ищете?   -  person Tomalak    schedule 30.09.2019
comment
В стрелочных функциях возврат неявный только при отсутствии фигурных скобок вокруг тела функции. Для фигурных скобок необходимо явно указать return. Все обратные вызовы .map() и некоторые обратные вызовы .then() выше должны быть рассмотрены в этом отношении.   -  person Roamer-1888    schedule 30.09.2019
comment
После внесения этих поправок вам также потребуется немного изменить код, чтобы избежать появления заявлений после того, как были сделаны возвраты.   -  person Roamer-1888    schedule 30.09.2019
comment
@ Томалак Извините, это так. Необходимо сделать .find() в соответствии с документацией API loopback4. Я действительно ценю ваше терпение и то, что вы нашли время, чтобы помочь мне.   -  person Vikram Khemlani    schedule 30.09.2019
comment
@ Roamer-1888, а обязательно ли возвращать вещи? Я думал, что, накопив его в один объект, мне не нужно будет возвращаться во время любого из .map(), поскольку я изменяю объект по мере продвижения?   -  person Vikram Khemlani    schedule 30.09.2019
comment
Да, абсолютно необходимо возвращать обещания из ваших .then() обратных вызовов (где они существуют). Таким образом цепочка информируется о внутренней асинхронной активности. Таким образом, вы должны делать эти возвраты, даже если вы не доставляете данные таким образом.   -  person Roamer-1888    schedule 30.09.2019
comment
И очень важно делать возвраты из обратных вызовов .map(), иначе вы будете отображать массив неопределенных значений. Promise.all() нужен массив промисов, чтобы укусить.   -  person Roamer-1888    schedule 30.09.2019
comment
Спасибо @Roamer-1888! Я понял, что мне нужны операторы возврата каждый раз, чтобы заставить его работать   -  person Vikram Khemlani    schedule 30.09.2019


Ответы (1)


Вам только нужно использовать async, когда вы используете await в той же функции. Если во вложенной функции есть await, родительская функция не нуждается в async.

Однако в вашем случае нет функции, которую нужно сделать async в первую очередь.

  • Нет никакой выгоды в ожидании каких-либо результатов в вашей функции, потому что никакой код внутри не зависит от какого-либо промежуточного результата. Просто возвращайте обещания по мере их получения.
  • Нет необходимости в промежуточных переменных результата, таких как orderFrameArray, вы делаете вещи сложнее, чем они есть, с вашим подходом ожидания отдельных ордеров и переноса их в переменную верхнего уровня.
  • Использование await в цикле, как вы делаете внутри вызова .map(), плохо сказывается на производительности. По сути, таким образом вы сериализуете доступ к базе данных — следующий запрос будет отправлен только после возврата текущего. Такое последовательное соединение сводит на нет способность базы данных обрабатывать несколько одновременных запросов.
  • getUserOrders2 это не Promise<any>, это Promise<Array<OrderFrame>>.
  • throw в любом случае завершает функцию, вы можете выполнить несколько проверок на наличие ошибок без использования else if. Это уменьшает вложенность.

Таким образом, полностью асинхронная функция будет выглядеть так:

getUserOrders2(@param.path.number('id') id: number): Promise<Array<OrderFrame>> {
  if (!this.user) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
  if (this.user.id != id) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);

  return this.restaurantRepository
    .orders(this.user.restaurantId).find().then(
      orders => Promise.all(orders.map(
        order => this.orderRepository.orderItems(order.id).find().then(
          order => new OrderFrame(order)
        )
      ))
    );
}

Эквивалент async/await этой функции будет более сложным.

Затем вы ожидаете результата в вызывающем коде, что вам все равно придется делать:

async test() {
  const orders = await foo.getUserOrders2(someUserId);
  // ...
}

// or

test() {
  foo.getUserOrders2(someUserId).then(orders => {
    // ...
  });
}
person Tomalak    schedule 30.09.2019
comment
Массив обещаний по-прежнему необходимо агрегировать с помощью Promise.all(). - person Roamer-1888; 30.09.2019
comment
Я обновил его на основе вашего решения, но почему-то возвращается только массив верхнего уровня. Пожалуйста, дайте мне знать, если у вас есть какие-либо советы - person Vikram Khemlani; 30.09.2019
comment
Я не знаю, что вы сделали, но вы точно ничего не обновили на основе моего решения. Ни один код, который вы показываете, не похож на код, который я написал. - person Tomalak; 30.09.2019
comment
Я использовал .then и Promise.all, как вы сказали, но я создал рамку порядка, которая собирает данные по мере повторения каждой карты, чтобы создать один большой объект, в который могут быть возвращены все данные в нем. - person Vikram Khemlani; 30.09.2019
comment
@Tomalak Когда я попробовал то, что вы сказали, он вернул самый внутренний вложенный объект, но каждый слой выше по какой-то причине был пуст. - person Vikram Khemlani; 30.09.2019
comment
@Tomalak На самом деле, я думаю, что теперь понимаю это. Большое спасибо за помощь - person Vikram Khemlani; 30.09.2019
comment
Я могу только порекомендовать вам разделить большую функцию на более мелкие. Создайте один с именем getOrderItemOptionValues(), который возвращает Promise<Array<Value>>, другой с именем orderItemOptions(), который возвращает Promise<Array<Option>>, и так далее. Таким образом, ваш код станет намного более управляемым, что будет огромным улучшением. Текущая функция getUserOrders2 слишком сложна. - person Tomalak; 30.09.2019
comment
Такое последовательное соединение сводит на нет способность базы данных обрабатывать несколько одновременных запросов. +1 — Хороший способ подчеркнуть тот факт, что такой подход приведет к абсурду;) - person iLuvLogix; 23.04.2021