Всем привет!
Как известно, есть несколько способов работы с асинхронными функциями в js. Например, ангуляр построен на библиотеке rxjs, которая использует паттерн Observable с его .subscribe. Так же в js в одной из последних спецификаций появился синтаксис async/await, основанный на промиссах. И мне нравится писать именно в таком виде. Более лаконично что ли.
К примеру, пишем api вызов вот так:
public async getUserRoles(userId: string) {
return this.get<number[]>('GetRoles?userId=' + userId).toPromise();
}
И далее можем вызывать в каких-то компонентах
this.userRoles = await this.service.getUserRoles(this.userId);
Далее, я хочу запилить прослойку. Сложить вызов какого-то апи вызова в локальное хранилище в глобальный сервис. Чтобы не дёргать апишку каждый раз. Например, список существующих пользователей:
export class UsersApiService extends BaseApiService {
private storage: UserProfileModel[] = null;
constructor(public http: HttpClient) {
super('Users', http);
}
public async GetProfiles() {
if (!this.storage) {
this.storage = (await this.get<any[]>('GetProfiles').toPromise()).map(x => new UserProfileModel(x));
}
return this.storage;
}
public async getUser(userId: string) {
const profiles = await this.GetProfiles();
return profiles.find(x => x.id == userId);
}
public async getUserRoles(userId: string) {
return this.get<number[]>('GetRoles?userId=' + userId).toPromise();
}
public async setUserRoles(userId: string, roles: number[]) {
return this.post('SetRoles', { userId: userId, roles: roles }).toPromise();
}
}
Хранилище storage, как мы тут видим, уже обычное, без промиссов и прочего. Обычный массив. Логика следующая: когда вызывается метод GetUser, этот метод (через await) получает массив всех пользователей. То есть, первый раз загружается через апи, но, если он уже содержит какой-то результат, апишка не дёргается, просто find по массиву отдаёт требуемого пользователя.
Для справки приведу так же класс UserProfileModel. В этой моделе присутствует вычисляемый геттер shortName
/** Пользователь */
export class UserProfileModel {
/** GUID */
public id: string;
/** Логин */
public userName: string;
/** Эмейл */
public email?: string;
/** Телефон */
public phone?: string;
constructor(obj: any = {}) {
this.id = obj.id;
this.email = obj.email;
this.phone = obj.phone;
this.userName = obj.userName;
}
get shortName(): string {
return this.userName || this.email || this.phone;
}
}
Проблем нет, разумеется, если я вызываю функцию GetUser в какой-либо асинхронной функции в обработчике .ts компонента. Но, если же я пытаюсь получить GetUser внутри html какого-либо компонента, совершенно ничего не получается. Даже если я использую pipe async. К примеру, отображаю список модели Issue, в которой есть поле с идентификатором пользователя:
<tr *ngFor="let el of dataSource">
<td class="col">
{{users.getUser(el.assignee).shortName | async}}
</td>
<td class="col">{{el.reporter}}
</td>
<td class="col">{{el.summary}}</td>
<td class="col">{{el.description}}</td>
<td>
<a [routerLink]='["/issue/edit", el.id]'>
<input type="submit" value="Edit" class="btn btn-primary">
</a>
</td>
</tr>
Страница просто виснет в бесконечном цикле. При этом, если смотреть через отладчик, js регулярно заходит в геттер shortName и, самое главное, с первого раза получает корректный результат, но на этом совершенно не хочет останавливается, долбит и долбит как дятел.
Расскажите, пожалуйста, как правильно писать в таких ситуациях?