Рекурсивное перебор промисов

У меня есть интерфейс REST API, который дает мне только первый уровень некоторой информации.

Так например я хочу собирать группы. Каждая группа может иметь подгруппы. Так, например, «Группа 1» имеет подгруппы «Группа А» и «Группа Б». «Группа А» имеет подгруппу «Группа X». И так далее.

Но API дает мне только первый уровень групп для имени группы. Поэтому я передаю «Группу 1» в API, и он возвращает «Группу A» и «Группу B». Чтобы получить вспомогательные группы группы А, мне нужно снова вызвать API. Но я не знаю, сколько итераций у этого будет.

Поэтому я подумал об использовании рекурсии, но я не продвинулся далеко.

Пока мой код:

getGroupChildren(group:string){ return this restService.getGroupChildren(group)}

getGroups():Promise<any>{
  let collection:string[] = [];
    return this.getGroupChildren("Group A").then((result)=> {
      if(result.data.length !==0){
         return this.getGroupChildren(data[0].groupName);
      }
    });
}

Теперь это вернет мне только первые Supgroups первого элемента.

Как я могу добиться того, чтобы всегда находили все Supgroup независимо от их количества? Может быть, хорошо использовать Observables?

Вот пример структуры одного вызова API:

{  "groupName" : "Group_1",  "children" : ["Group_A", "Group_B"]}

person user5417542    schedule 19.03.2018    source источник
comment
Как выглядят ваши данные? Как узнать, есть ли еще уровни для углубления?   -  person Bunyamin Coskuner    schedule 19.03.2018
comment
Вы пытались поместить отладчик внутри then обратного вызова getGroupChildren и посмотреть, что произойдет после 1-й итерации? какое значение вы получаете/передаете для data[0].groupName?   -  person palaѕн    schedule 19.03.2018
comment
Это может быть дубликат stackoverflow.com/questions/46863275/   -  person Grzegorz Gajos    schedule 19.03.2018
comment
В том-то и дело, что я не знаю, сколько уровней будет. Поэтому мне нужно это динамически. Я буду вызывать для каждой группы ресурс и ее дочерние элементы, и если я получу пустой массив, это означает, что для этой группы больше нет подгрупп, и я останавливаюсь.   -  person user5417542    schedule 19.03.2018
comment
Маловероятно, что observable предложит что-то другое. Это обычная рекурсивная функция. Я бы предложил пойти async вместо необработанных обещаний, чтобы упростить задачу. Фактическое решение неясно, потому что вопрос не содержит достаточно данных. Что именно должна возвращать функция? Он называется getGroups, но возвращает одну строку.   -  person Estus Flask    schedule 19.03.2018
comment
Я добавил пример ответа. Я возвращаю объект с массивом строк данных.   -  person user5417542    schedule 19.03.2018
comment
На сервере нельзя? Работа внешнего интерфейса, если у него есть вся необходимая информация, - не самая эффективная практика, когда это может привести к множеству вызовов REST.   -  person StudioTime    schedule 19.03.2018
comment
Какую конечную структуру вы хотите получить? Плоский массив всех групп, только листьев дерева групп или некоторая вложенная структура объектов?   -  person trincot    schedule 19.03.2018
comment
Просто массив строк из всех групп. Но я думаю, что даже если я захочу другую структуру, я не думаю, что это будет проблемой. Я хочу вызывать Promise только до тех пор, пока не перестану получать данные.   -  person user5417542    schedule 19.03.2018
comment
Хорошо, я ответил структурой объекта, поскольку плоский массив может вызвать вопрос о порядке, а затем узнать, какие отношения родитель-потомок. Структура вложенных объектов содержит информацию, необходимую для создания из нее плоского массива.   -  person trincot    schedule 19.03.2018


Ответы (2)


Вы можете добиться того, чего хотите, с помощью flatMap оператора Observable

getGroups(group: string) {

    return this.http.get(`/group/{group}`).flatMap(response => {
        if (response.children.length === 0) { // you hit a leaf, stop recursion here
             return Observable.of(response);
        } else { // there are more levels to go deeper
             return this.getGroups(response.children[0].groupName);
        }
    });
}

Редактировать с помощью Promise

Допустим, вы используете GroupService, который возвращает данные вместо HttpClient. Вы можете преобразовать Promise в Observable с помощью оператора fromPromise.

getGroups(group: string) {

    return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
        if (response.children.length === 0) { // you hit a leaf, stop recursion here
             return Observable.of(response);
        } else { // there are more levels to go deeper
             return this.getGroups(response.children[0].groupName);
        }
    });
}

Редактировать 2 Использование этой службы

Давайте посмотрим на ваш пример. У вас есть следующие json

{
    "groupName": "Group_1", 
    "children" : ["Group_A", "Group_B"]
}

В вашем файле компонента вы вызываете службу следующим образом

...
this.recursiveGroupService.getGroups("Group_1")
    .subscribe(response => {
        // at this point response will be `Group_A`
    })

Редактировать 3 Получение всего объекта

На этот раз мы будем использовать forkJoin и вызывать getGroups для всех дочерних элементов и собирать результаты в массив children.

Примечание. Я сам не тестировал этот код. Он может содержать некоторую ошибку. Если это так, дайте мне знать.

import { forkJoin, of } from 'rxjs';
import { map } from 'rxjs/operators';

getGroups(group: string) {
    let retVal;
    return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
        retVal = {
             groupName: response.groupName
        };
        if (response.children.length === 0) { // you hit a leaf, stop recursion here
             return of(retVal);
        } else { // there are more levels to go deeper
             // this will create list of observable for each child
             const children$ = response.children.map(
                       child => this.getGroups(child)); 
             // forkJoin will execute these observables in parallel
             return forkJoin(children$).pipe(
                  map(results => {
                      // results is an array containing children data
                      retVal.children = results;

                      return retVal;
                  })
             );
         }
    });
}
person Bunyamin Coskuner    schedule 19.03.2018
comment
Выглядит хорошо. Но поскольку я мало что сделал с Observables, как мне это преобразовать? Я использую Framework, который возвращает только Promises, поэтому у меня есть только класс обслуживания, который предоставляет мне метод типа Promise для вызова API. - person user5417542; 19.03.2018
comment
Вы всегда можете использовать Observable.fromPromise, который преобразует Promise в Observable. остальное в моем ответе - person Bunyamin Coskuner; 19.03.2018
comment
Хорошо, а затем я подписываюсь на это в своем коде, где я вызываю getGroups()? Извините, но я очень новичок в Observables. - person user5417542; 19.03.2018
comment
Благодаря рекурсивной части работает отлично. Однако я все еще сталкиваюсь с проблемами с моей структурой вывода. В вашем примере я получу только потомков последнего узла. Но так как в конце концов мне нужно полное дерево, я как бы ищу простой способ перебрать их. - person user5417542; 19.03.2018
comment
@user5417542 user5417542 Я обновил свой ответ, пожалуйста, проверьте его. - person Bunyamin Coskuner; 19.03.2018
comment
Я получаю сообщение об ошибке в строке forkjoin().pipe(), она говорит, что не может найти функцию карты - person user5417542; 19.03.2018
comment
Вы import { map } from 'rxjs/operators'; ? - person Bunyamin Coskuner; 19.03.2018
comment
Спасибо, ты гений :). Работает идеально. Теперь мне нужно выяснить, как именно это работает. Есть ли хороший ресурс для этого? - person user5417542; 19.03.2018
comment
Я рад, что это работает :) Вы можете отметить это как принятое? Есть тонны документации по RxJs. Вы должны посмотреть, как использовать операторы RxJ, такие как flatMap forkJoin и другие. Это дерево решений также может помочь -› reactivex.io/documentation/operators.html#tree - person Bunyamin Coskuner; 19.03.2018

Вы можете использовать Promise.all для рекурсивного разрешения более глубоких дочерних элементов, а затем взять результат (массив) для создания объекта для разрешения обещания с помощью:

getGroups(groupName = "Group A") {
    return this.getGroupChildren(groupName).then((result) =>
        Promise.all(result.data.map( ({groupName}) => this.getGroups(groupName) ))
    ).then(children => ({ groupName, children }));
}

Таким образом, обещанное значение может быть примерно таким:

[{
    groupName: "Group A",
    children: [{
        groupName: "Group A1",
        children: []
    },  {
        groupName: "Group A2",
        children: []
    }]
}]
person trincot    schedule 19.03.2018