Angular/Ionic 链接 HTTP 调用不同步

Angular/Ionic Chaining HTTP Calls Out of Sync

我目前正在使用 API 这有点痛苦 :D API 没有 return 我的应用程序所需的完整信息,这意味着我有拨打多个电话以获得所需的全部信息。另外,我正在努力保持头脑清醒,所以如果它没有得到很好的解释,请告诉我!

发行的主要详情

当前的 API 流程看起来有点像这样:

  1. 获取“组 ID”列表(参见响应 1)。

  2. 使用该列表,针对每个组 ID,获取组中的组详细信息和类型(请参阅响应 2)。

  3. 使用组详细信息,对于每种类型,获取类型名称(参见响应 3)。

  4. 用所有的细节建造一棵大树。

  5. 使用单独的端点,获取所有 'skills' 并相应地更新树(参见响应 4)。

问题出现在尝试 return 正确位置的正确值时,由于我在异步承诺中嵌套承诺,因此完全不同步 :O

可以在 https://esi.tech.ccp.is/latest/.

找到主要的 API 端点和示例

我当前的代码看起来有点像下面的代码(我试图按照调用它们的顺序列出函数)。

问题是,我需要定位点:

技能树目标:

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

tab-skills-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 Provider - getSkillGroups() - returns [...] of group ID's see response 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) - returns {...} 组详细信息请参阅响应 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(returns群详情及群内类型):

{
  "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(returns 按 ID 键入详细信息):

{
  "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"
    ]
  }
}

你应该使用 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 而不是 promises 像这样的大案例,因为 Observable 允许你做更多的事情,并且使用 do 运算符无需更改数据即可轻松调试。

有关 rxjs officiel documentation website 的更多详细信息。

我正在寻找一些方法来完成异步调用序列,并找到了这个男孩的文章。简而言之:使用 Observables 的 class forkJoin 方法。

Look for the section named forkJoin