Порядок выполнения операций в асинхронных циклах js

Есть асинхронная функция buildDependencyTree, которая должна построить дерево зависимостей в формате объектов на основе объекта, содержащего имена корневых пакетов. Она принимает на вход этот исходный объект и асинхронную функцию fetcher, которая принимает имя пакета и возвращает список его зависимостей.

Функция использует цикл const key in object, который запрашивает список зависимостей для каждого ключа объекта (пакета), записывает элементы списка, как ключи вложенного объекта и затем рекурсивно вызывает функцию buildDependencyTree с этим новым объектом.

async function buildDependencyTree(dependenciesList, fetcher) {
  //функция для запроса зависимостей пакета
  async function getdependencies(package) {
    return fetcher(package)
      .then(response => {
        return response
      })
      .catch(err => console.log(err))
  }
  //цикл, проходящий по ключам текущего объекта
  for (const item in dependenciesList) {
    let refToItem = dependenciesList[item] //ссылка на текущий объект(пока пустой)
    let r = await getdependencies(item) //запрос зависимостей для текущего ключа
    console.log(item, refToItem, r, dependenciesList, '1')

    if (r.dependencies.length !== 0) { //если зависимости найдены добавляем их как ключи в объект и ставим значение {}
      r.dependencies.forEach(dep => refToItem[dep] = {})
      console.log(refToItem, r, dependenciesList, '2')
      buildDependencyTree(refToItem, fetcher) // вызываем функцию со следующим (вложенным) объектом
    }

  }
  return dependenciesList
}
//исходный объект с пакетами
const deps = {
  "aroot": {},
  "broot": {}
}
//функция задержки
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
//функция, возвращающая необходимый список зависимостей
async function fetcher(package) {
  await wait(1000);
  let response
  switch (package) {
    case "aroot":
      response = {
        package: 'aroot',
        dependencies: ["npm", "yup"]
      }
      break
    case "broot":
      response = {
        package: 'broot',
        dependencies: []
      }
      break
    case "npm":
      response = {
        package: 'npm',
        dependencies: ["abc", "def", "ghi"]
      }
      break
    case "yup":
      response = {
        package: 'yup',
        dependencies: ["abc", "mno"]
      }
      break
    case "abc":
      response = {
        package: 'abc',
        dependencies: ["xyz"]
      }
      break
    case "def":
      response = {
        package: 'def',
        dependencies: []
      }
      break
    case "ghi":
      response = {
        package: 'ghi',
        dependencies: []
      }
      break
    case "mno":
      response = {
        package: 'mno',
        dependencies: []
      }
      break
    case "xyz":
      response = {
        package: 'xyz',
        dependencies: []
      }
      break
  }
  return response
}
//Тестовый вызов функции
buildDependencyTree(deps, fetcher)
  .then(dependencyTree => console.log(dependencyTree, 'end'))
  .catch(err => console.log(err.message, 'err'))

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


Ответы (1 шт):

Автор решения: Grundy
buildDependencyTree(refToItem, fetcher) // вызываем функцию со следующим 

В этом месте происходит вызов асинхронной функции, но не ожидается ответ.

→ Ссылка