Angular/Ionic Chaining HTTP-звонки не синхронизированы
В настоящее время я работаю с API, который немного мучит:D API не возвращает полную информацию, необходимую для моего приложения, и это означает, что мне нужно сделать несколько вызовов, чтобы получить всю необходимую информацию. Кроме того, я изо всех сил стараюсь держать это в голове, поэтому, если это не очень хорошо объяснено, просто дайте мне знать!
Основные детали выпуска
Текущий поток API выглядит примерно так:
Получить список "идентификаторов групп" (см. Ответ 1).
Используя этот список, для каждого идентификатора группы получите сведения о группе и ее типы (см. Ответ 2).
Используя сведения о группе, для каждого типа получите имя типа (см. Ответ 3).
Постройте большое дерево со всеми деталями.
Используя отдельную конечную точку, получите все "навыки" и обновите дерево соответствующим образом (см. Ответ 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.