Angular/Ionic Chaining HTTP-звонки не синхронизированы

В настоящее время я работаю с API, который немного мучит:D API не возвращает полную информацию, необходимую для моего приложения, и это означает, что мне нужно сделать несколько вызовов, чтобы получить всю необходимую информацию. Кроме того, я изо всех сил стараюсь держать это в голове, поэтому, если это не очень хорошо объяснено, просто дайте мне знать!

Основные детали выпуска

Текущий поток API выглядит примерно так:

  1. Получить список "идентификаторов групп" (см. Ответ 1).

  2. Используя этот список, для каждого идентификатора группы получите сведения о группе и ее типы (см. Ответ 2).

  3. Используя сведения о группе, для каждого типа получите имя типа (см. Ответ 3).

  4. Постройте большое дерево со всеми деталями.

  5. Используя отдельную конечную точку, получите все "навыки" и обновите дерево соответствующим образом (см. Ответ 4).

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

Основные конечные точки и примеры API можно найти по адресу https://esi.tech.ccp.is/latest/.

Мой текущий код выглядит примерно так, как показано ниже (я попытался перечислить функции в порядке их вызова).

Проблема в том, что мне нужно найти точку, где:

  • Список групп был возвращен.

  • Для каждой группы включенные типы были возвращены.

  • к объекту skillTree добавлено новое свойство в следующем формате.

Цель Древа Навыков:

skillTree = {
    "groupName": [
        "skillID": {
            "level": 0;
        },
        "skill2ID": {
            "level": 0;
        },...
    ],
    "group2Name": [
        "skillID" {
            "level": 0;
        },...
    ],...
};

tab-skill-all.ts (вызывает основную функцию):

        eveESI.buildSkillTree().then(() => { 
            // Need to add names to all skills in tree...
            console.log('Completed skill tree:');
            console.log(eveESI.skillTree);
        }).catch((error) => { 
            // Do error handling...
        });

Поставщик eveESI - buildSkillTree():

     buildSkillTree(){
        return new Promise((resolve, reject) => {
            this.getSkillGroups().then((groups) => { 
                console.log('Success: Fetched groups successfully!');
                console.log(groups);

                // Process groups. First get group details including types. Then for each group push to main array.
                for (var i in groups) {
                    if (groups.hasOwnProperty(i)) {
                        this.getSkillsInGroup(groups[i]).then((data) => { 

                            var groupDetails = JSON.parse(data.toString());

                            var types = groupDetails.types;
                            var name = groupDetails.name;

                            console.log('Success: Fetched types for group ' + name + ' successfully!');

                            // Declare and build temp group object before we push it to main skill object...
                            var tempGroupObj = [];

                            // For each skill type in the group add to temporary array...
                            for (var n in types) {
                                if (types.hasOwnProperty(n)) {
                                    tempGroupObj[types[n]] = {};
                                    tempGroupObj[types[n]]['level'] = 0;
                                }
                            }

                            console.log(tempGroupObj);

                            this.skillTree[name] = tempGroupObj;

                        }).then(() => {

                        }).catch((error) => { 
                            // Do error handling...
                            console.log(error);
                        });
                    }
                }

                resolve();
            }).catch((error) => { 
                // Do error handling...
                reject();
            });
        });
    }

Поставщик eveESI - getSkillGroups() - возвращает [...] идентификаторов групп, см. ответ 1:

     getSkillGroups(){
        return new Promise((resolve, reject) => {
            this.http.get(this.apiRoot + 'universe/categories/16/', { }, { Authorization: 'Basic YWUxYmIzZDU4ZmRiNDk1ZDk3ZTE1ZTE0OTIyZDc0ZDk6MnpsVjNLZzVHbTh4OHY5b2lUSENYOHVXR21PYjlHd2Rqc3htQ0NHOA=='})
            .then(reqResponse => {
                // Returns {} of skill groups from category...
                var responseJSON = JSON.parse(reqResponse.data);

                resolve(responseJSON.groups);
            }).catch(reqError => {
                // Error. Return error message...
                reject();
            });
        });
    }

Поставщик eveESI - getSkillsInGroup(id) - возвращает {...} сведений о группе, см. ответ 2:

    getSkillsInGroup(id){
        return new Promise((resolve, reject) => {
            this.http.get(this.apiRoot + 'universe/groups/' + id + '/', { }, { Authorization: 'Basic YWUxYmIzZDU4ZmRiNDk1ZDk3ZTE1ZTE0OTIyZDc0ZDk6MnpsVjNLZzVHbTh4OHY5b2lUSENYOHVXR21PYjlHd2Rqc3htQ0NHOA=='})
            .then(reqResponse => {
                resolve(reqResponse.data);
            }).catch(reqError => {
                // Error. Return error message...
                reject();
            });
        });
    }

Ответ 1 (ID группы списков):

{
  "category_id": 16,
  "name": "Skill",
  "published": true,
  "groups": [
    255,
    256,
    257,
    258,
    266,
    268,
    269,
    270,
    272,
    273,
    274,
    275,
    278,
    505,
    1209,
    1210,
    1213,
    1216,
    1217,
    1218,
    1220,
    1240,
    1241,
    1545
  ]
}

Ответ 2 (возвращает сведения о группе и типы в группе):

{
  "group_id": 255,
  "name": "Gunnery",
  "published": true,
  "category_id": 16,
  "types": [
    3300,
    3301,
    3302,
    3303,
    3304,
    3305,
    3306,
    3307,
    3308,
    3309,
    3310,
    3311,
    3312,
    3315,
    3316,
    3317,
    11082,
    11083,
    11084,
    12201,
    12202,
    12203,
    12204,
    12205,
    12206,
    12207,
    12208,
    12209,
    12210,
    12211,
    12212,
    12213,
    12214,
    12215,
    20327,
    21666,
    21667,
    22043,
    24563,
    32856,
    41403,
    41404,
    41405,
    41406,
    41407,
    41408,
    41537
  ]
}

Ответ 3 (возвращает информацию о типе по идентификатору):

{
  "type_id": 3300,
  "name": "Gunnery",
  "description": "Basic turret operation skill. 2% Bonus to weapon turrets' rate of fire per skill level.",
  "published": true,
  "group_id": 255,
  "market_group_id": 364,
  "radius": 1,
  "volume": 0.01,
  "packaged_volume": 0.01,
  "icon_id": 33,
  "capacity": 0,
  "portion_size": 1,
  "mass": 0,
  "dogma_attributes": [...],
  "dogma_effects": [...]
}

Package.json

{
  "name": "name",
  "version": "0.0.1",
  "author": "author",
  "homepage": "http://ionicframework.com/",
  "private": true,
  "scripts": {
    "clean": "ionic-app-scripts clean",
    "build": "ionic-app-scripts build",
    "lint": "ionic-app-scripts lint",
    "ionic:build": "ionic-app-scripts build",
    "ionic:serve": "ionic-app-scripts serve"
  },
  "dependencies": {
    "@angular/common": "5.0.3",
    "@angular/compiler": "5.0.3",
    "@angular/compiler-cli": "5.0.3",
    "@angular/core": "5.0.3",
    "@angular/forms": "5.0.3",
    "@angular/http": "5.0.3",
    "@angular/platform-browser": "5.0.3",
    "@angular/platform-browser-dynamic": "5.0.3",
    "@ionic-native/browser-tab": "^4.4.2",
    "@ionic-native/core": "4.4.0",
    "@ionic-native/deeplinks": "^4.4.2",
    "@ionic-native/http": "^4.4.2",
    "@ionic-native/secure-storage": "^4.4.2",
    "@ionic-native/spinner-dialog": "^4.4.2",
    "@ionic-native/splash-screen": "4.4.0",
    "@ionic-native/sqlite": "^4.4.2",
    "@ionic-native/sqlite-porter": "^4.5.0",
    "@ionic-native/status-bar": "4.4.0",
    "@ionic/storage": "^2.1.3",
    "angular2-natural-sort": "0.0.2",
    "angular2-swagger-client-generator": "0.0.22",
    "cordova-android": "6.3.0",
    "cordova-plugin-advanced-http": "^1.9.0",
    "cordova-plugin-browsertab": "^0.2.0",
    "cordova-plugin-compat": "^1.2.0",
    "cordova-plugin-device": "^1.1.4",
    "cordova-plugin-file": "^5.0.0",
    "cordova-plugin-ionic-webview": "^1.1.16",
    "cordova-plugin-native-spinner": "^1.1.3",
    "cordova-plugin-secure-storage": "^2.6.8",
    "cordova-plugin-splashscreen": "^4.0.3",
    "cordova-plugin-statusbar": "^2.3.0",
    "cordova-plugin-whitelist": "^1.3.1",
    "cordova-sqlite-storage": "^2.1.2",
    "ionic-angular": "3.9.2",
    "ionic-plugin-deeplinks": "^1.0.15",
    "ionic-plugin-keyboard": "^2.2.1",
    "ionicons": "3.0.0",
    "ngx-order-pipe": "^1.1.1",
    "rxjs": "5.5.2",
    "sw-toolbox": "3.6.0",
    "swagger-angular-generator": "^1.2.1",
    "uk.co.workingedge.cordova.plugin.sqliteporter": "^1.0.2",
    "zone.js": "0.8.18"
  },
  "devDependencies": {
    "@ionic/app-scripts": "3.1.4",
    "typescript": "2.4.2"
  },
  "description": "An Ionic project",
  "cordova": {
    "plugins": {
      "ionic-plugin-keyboard": {},
      "cordova-plugin-whitelist": {},
      "cordova-plugin-device": {},
      "cordova-plugin-splashscreen": {},
      "cordova-plugin-ionic-webview": {},
      "cordova-plugin-browsertab": {},
      "ionic-plugin-deeplinks": {
        "URL_SCHEME": "_CUSTOMURLSCHEME",
        "DEEPLINK_SCHEME": "https",
        "DEEPLINK_HOST": "localhost",
        "ANDROID_PATH_PREFIX": "/",
        "ANDROID_2_PATH_PREFIX": "/",
        "ANDROID_3_PATH_PREFIX": "/",
        "ANDROID_4_PATH_PREFIX": "/",
        "ANDROID_5_PATH_PREFIX": "/",
        "DEEPLINK_2_SCHEME": " ",
        "DEEPLINK_2_HOST": " ",
        "DEEPLINK_3_SCHEME": " ",
        "DEEPLINK_3_HOST": " ",
        "DEEPLINK_4_SCHEME": " ",
        "DEEPLINK_4_HOST": " ",
        "DEEPLINK_5_SCHEME": " ",
        "DEEPLINK_5_HOST": " "
      },
      "cordova-plugin-secure-storage": {},
      "cordova-plugin-native-spinner": {},
      "cordova-plugin-advanced-http": {},
      "cordova-sqlite-storage": {},
      "cordova-plugin-statusbar": {},
      "uk.co.workingedge.cordova.plugin.sqliteporter": {}
    },
    "platforms": [
      "android"
    ]
  }
}

2 ответа

Решение

Вы должны использовать Observables, потому что Observables холодные, так что вы можете создать их, сохранить их в массиве, а затем объединить результаты, чтобы ваши http-запросы выполнялись одновременно, собирая детали для всего, что вам нужно.

Вот в псевдокоде (поскольку я не буду копировать всю вашу реализацию) цепочка Observable, решающая вашу проблему:

getSkillGroups() {
    this.http.get(this.apiRoot + 'universe/categories/16/', {}, {Authorization: 'Basic YWUxYmIzZDU4ZmRiNDk1ZDk3ZTE1ZTE0OTIyZDc0ZDk6MnpsVjNLZzVHbTh4OHY5b2lUSENYOHVXR21PYjlHd2Rqc3htQ0NHOA=='})
        .switchMap(response1 => {
            const groupsDetails: Observable<any>[] = [];
            response1.groups.forEach(group => {
                groupsDetails.push(this.getSkillsInGroup(group));
            });
            //This will return an array of requests ready to be fired right when you call subscribe on it
            // When fired, the requests will be parallels, not sync.
            return Observable.combineLatest(groupsDetails);
        });
}

getSkillsInGroup(id){
    return this.http.get(this.apiRoot + 'universe/groups/' + id + '/', { }, { Authorization: 'Basic YWUxYmIzZDU4ZmRiNDk1ZDk3ZTE1ZTE0OTIyZDc0ZDk6MnpsVjNLZzVHbTh4OHY5b2lUSENYOHVXR21PYjlHd2Rqc3htQ0NHOA=='})
        .map(response2 => response2.data);
}

Как только вы позвоните getSkillGroups(), вы получите массив response2данные, что означает, что вы можете добавить новый map Оператор, создавая массив Observables, чтобы добавить детали к навыкам. Тогда вы можете подписаться на него, и вы получите навыки.

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

Более подробная информация на сайте документации rxjs officiel.

Я искал способ сделать последовательность асинхронных вызовов и попал в статью об этом мальчике. Вкратце: используйте метод forkJoin класса Observables.

Ищите раздел с именем forkJoin

Другие вопросы по тегам