Как пропустить итерацию в наблюдаемых объектах .map ionic 4

Я работаю над приложением Ionic 4, которое извлекает сообщения из API WordPress. Я получаю избранное изображение, используя mergeMap и forkjoin, чтобы получить избранное изображение сообщений на главной странице сообщений.

Следующий код отлично работает, когда есть избранные изображения, но если нет избранных изображений, я просто получаю сообщение об ошибке от своего throwError. Кажется, я не могу зарегистрировать ошибку.

ERROR Something went wrong ;)

Вот мои файлы home.page.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { LoadingController } from '@ionic/angular';
import { WordpressRestapiService, Post } from '../services/wordpress-restapi.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  categoryId: number;
  private posts : Post[] = [];

  constructor(
    public loadingController: LoadingController, 
    private router: Router,
    private wordpressService: WordpressRestapiService) { }

  async ngOnInit() {
    const loading = await this.loadingController.create();
    await loading.present();

    this.loadPosts().subscribe((posts: Post[]) => {
      this.posts = posts;
      loading.dismiss();
    });
  }

  loadPosts() {
    return this.wordpressService.getRecentPosts(this.categoryId);
  }

  openPost(postId) {
    this.router.navigateByUrl('/post/' + postId);
  }
}

Вот мой служебный файл для использования wordpress api

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, forkJoin, throwError, empty } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class WordpressRestapiService {

  baseRestApiUrl: string = 'http://example.com/wp-json/wp/v2/';

  constructor(private httpClient: HttpClient) { }

  getRecentPosts(categoryId: number, page: number = 1): Observable<any[]> {
    // Get posts by a category if a category id is passed
    let category_url = categoryId ? ("&categories=" + categoryId) : "";

    return this.httpClient.get(this.baseRestApiUrl + "posts?page=" + page + category_url).pipe(
      map((res: any) => res),
      mergeMap((posts: any[]) => {
        if (posts.length > 0) {
          return forkJoin(
            posts.map((post: any) => {
              if (post.featured_media === 0) {
                console.log('fired');
                post.media = {};
                return new Post(post);
              }
              else {
                return this.httpClient.get(this.baseRestApiUrl + "media/" + post.featured_media).pipe(
                  map((res: any) => {
                    let media: any = res;
                    post.media =  new Media(media);
                    return new Post(post);
                  }),
                  catchError(error => {
                    return throwError('Something went wrong ;)');
                  })
                );
              }
            })
          );
        }
        return empty();
      }),
      catchError(error => {
        return throwError('Something went wrong ;)');
      })
    );
  }
}

export class Post {
  author: number;
  categories: number[];
  comment_status: string;
  content: object;
  date: string;
  date_gmt: string;
  excerpt: object;
  featured_media: number;
  format: string;
  guid: object;
  id: number;
  link: string;
  media: object;
  meta: object;
  modified: string;
  modified_gmt: string;
  ping_status: string;
  slug: string;
  status: string;
  sticky: boolean;
  tags: number[];
  template: string;
  title: object;
  type: string;
  _links: object;

  constructor(values: Object = {}) {
    Object.assign(this, values);
  }
}

export class Media {
  date: string;
  date_gmt: string;
  guid: object;
  id: number;
  link: string;
  modified: string;
  modified_gmt: string;
  slug: string;
  status: string;
  type: string;
  title: object;
  author: number;
  comment_status: string;
  ping_status: string;
  meta: object;
  template: string;
  alt_text: string;
  caption: object;
  description: object;
  media_type: string;
  mime_type: string;
  media_details: object;
  post: number;
  source_url: string;

  constructor(values: Object = {}) {
    Object.assign(this, values);
  }
}

У меня есть проверка, что если post.featured_media === 0, то просто верните сообщение, иначе сделайте вызов API, чтобы получить избранные изображения, но это никогда не возвращает сообщения. вызывается console.log('fired'), но loadCtrl никогда не закрывается, и сообщения никогда не отображаются.

Как я могу просто вернуть пустой объект для post.media, если нет избранного изображения, но вернуть все сообщения?

Обновление: на основании того, что сказал Дэвид, я обновил функцию getRecentPosts() в WordpresRestapiService следующим образом. Это дает мне результат, который я искал.

getRecentPosts(categoryId: number, page: number = 1): Observable<any> {
// Get posts by a category if a category id is passed
let category_url = categoryId ? ("&categories=" + categoryId) : "";

return this.httpClient.get(this.baseRestApiUrl + "posts?page=" + page + category_url).pipe(
  map((res: any) => res),
  mergeMap((posts: Post[]) => {
    if (posts.length > 0) {
      return forkJoin(
        posts.map((post: Post) => {
          if (post.featured_media === 0) {
            post.media = new Media;
            return of(new Post(post));
          }
          else {
            return this.httpClient.get(this.baseRestApiUrl + "media/" + post.featured_media).pipe(
              map((res: any) => {
                post.media = new Media(res);
                return new Post(post);
              }),
              catchError(val => of(val))
            );
          }
        })
      );
    }
    return empty();
  }),
  catchError(val => of(val))
);

}


person Danny Connolly    schedule 22.02.2019    source источник


Ответы (1)


Я думаю, проблема в том, что вы добавляете сообщение, а не наблюдаемое сообщение в массив, который вы передаете в forkJoin.

Вы могли бы попробовать,

import { Observable, forkJoin, throwError, empty, of } from 'rxjs';
...
if (post.featured_media === 0) {
  console.log('fired');
  post.media = {};
  return of(new Post(post));
}

Кстати, я бы не стал использовать catchError таким образом. По сути, он поглощает любые ошибки javascript (а также неудачные ответы HTTP). Вот почему вы не видите полезной ошибки в этом случае.

Ссылки: (это не официальные документы, но я нахожу их более читабельными) Переполнение стека считает, что эти ссылки являются кодом...

https://www.learnrxjs.io/operators/combination/forkjoin.html
https://www.learnrxjs.io/operators/creation/of.html
https://www.learnrxjs.io/operators/error_handling/catch.html
person David Synnott    schedule 22.02.2019
comment
Все еще не на 100% в catchError, но я подумал, что это, вероятно, другой вопрос сам по себе. Спасибо за вашу помощь. - person Danny Connolly; 25.02.2019
comment
Нет проблем @DannyConnolly. В основном то, что происходило, это то, что forkJoin ожидал массив Observables в качестве входных данных. В какой-то момент он выдаст ошибку, либо преднамеренно, потому что это проверка типа ввода, либо он попытается сделать что-то с вашим сообщением, что вызовет исключение. В этот момент эта ошибка всплывает в вашем коде. Если catchError() нет, вы, вероятно, увидите эту ошибку где-нибудь (например, в инструментах разработчика вашего браузера). С catchError обычное выполнение продолжается после запуска содержимого catchError (в этом случае регистрируется «Что-то пошло не так ;)». - person David Synnott; 25.02.2019