在某些数据的映射中获取异步资源的正确方法是什么?

What is the correct way to obtain an asynchronous resource in the mapping of some data?

根据以下数据集,我尝试用我可以在模板中使用的值替换 logoUrl 属性 的值,这是 S3 中受保护的 blob 资源

[
  {
    id: 2,
    name: "ABC",
    logoUrl: null,
  },
  {
    id: 1,
    name: "DEF",
    logoUrl:
      "https://assets.demo.app.net/bucket/154F460F8FAE344F13C29962931CBD3C1F1384F7A9DF9F548D23CF4AFF5E6811-some-name.jpeg",
  },
  {
    id: 3,
    name: "GHI",
    logoUrl:
      "https://assets.demo.app.net/bucket/4CA2F69627B51F7F07A39642DB3538A3D55564891F456528067B47DCEED61B4F-some-name.png",
  },
  {
    id: 6,
    name: "JKL",
    logoUrl: null,
  },
];

为此我尝试实现以下解决

@Injectable({
  providedIn: 'root',
})
export class FetchDataResolver implements Resolve<Model[]> {
  constructor(
    private dataService: DataService,
    private s3Service: S3Service
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<Model[]> {
    return this.dataService
      .getAll()
      .pipe(
        map((data) => data.sort((a, b) => a.name.localeCompare(b.name)))
      );
  }
}

我要解决的问题是.pipe()里面如何映射从S3获取资源的响应,然后替换记录。因为第一个动作是异步操作。

S3 服务有两个方法,一个是负责获取资源是一个blob,另一个是负责获取一个url,我可以在模板中使用它来显示图像。服务如下

@Injectable({
  providedIn: 'root',
})
export class S3Service {
  constructor(
    private domSanitizer: DomSanitizer,
    private httpClient: HttpClient
  ) {}

  getResource(resource: string): Observable<Blob> {
    return this.httpClient.get(resource, { responseType: 'blob' });
  }

  getSourceFromBlob(blob: Blob): SafeUrl {
    return this.domSanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob));
  }
}

预期结果类似如下

[
  {
    id: 2,
    name: "ABC",
    logoUrl: null,
  },
  {
    id: 1,
    name: "DEF",
    logoUrl:
      "blob:http://localhost:4200/e9113189-d08e-485c-b61a-5a8f5f4a0fdd",
  },
  {
    id: 3,
    name: "GHI",
    logoUrl:
      "blob:http://localhost:4200/e9113189-d08e-485c-b61a-5a8f5f4a0fdd",
  },
  {
    id: 6,
    name: "JKL",
    logoUrl: null,
  },
];

让我们尝试对情况有一个更高层次的了解。

你有一个流,它给你 Observable<Model[]>

从该流中,您想获得一个具有完全相同形状但已更新 1 属性 (logoUrl) 的新流。

为此,您需要为数组的每个项目执行异步操作。

在这种情况下,几乎是第一个考虑它的运营商 forkJoin。该运算符让您传递一组可观察对象,并将返回一个包含所有数组的流。所以换句话说:传递一个 Array<Observable<T>> 你会得到 Observable<Array<T>>.

如果您很难想到那个,而您又熟悉 Promise.all,那么它非常相似。

return this.dataService.getAll().pipe(
  map((data) => data.sort((a, b) => a.name.localeCompare(b.name))),
  switchMap((data) => {
    // for each entry...
    const dataWithSourceLogoUrl$ = data.map((x) => {
      this.s3Service
      // let's fetch the source logo
        .getSourceFromBlob(x.logoUrl)
        // and merge it back to the original object
        .pipe(pipe(map((sourceLogoUrl) => ({ ...x, logoUrl: sourceLogoUrl }))));
    });
    
    // finally, use forkJoin to start all the streams we prepared above
    return forkJoin(dataWithSourceLogoUrl$);
  })
);

希望代码中的注释足够了,但如果不够请告诉我,我会编辑以解释更多。