在使用 mergeMap 上传所有文件后启动 http post

Starting a http post after all files are uploaded with a mergeMap

我正在尝试在所有文件上传后发出 POST 请求。在我的网络表单中,用户可以添加许多文件来上传。我有两个 API 来处理我的请求。

一个API用于我的fileupload,它在数据库中保存文件,returns一个id(文档id),第二个API保存档案。在调用 savedossier API 之前,我必须将所有文档 ID 添加到档案对象中。

我用 mergeMap 上传文件:

const fileupload$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
);

fileupload$.subscribe(
  (data) => this.dossier.attachments.push(data),
  (err) => console.error('err: ' + err)
);

const savedossier$ = this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);

savedossier$.subscribe(
  (data) => console.log('Dossier saved!'),
  (err) => console.error('err: ' + err)
);

我的问题是第二次调用(对 savedossier$)没有等到所有文件都上传完毕(因为它是异步的)。所以文档 ID 没有添加到我的档案对象中。

我已经用这样的 "ugly" 解决方案进行了测试:

const fileupload$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
);

fileupload$.subscribe(
  (data) => onuploaded(data),
  (err) => console.error('err: ' + err)
);

onuploaded(data) {
  this.dossier.attachments.push(data)
  if (this.dossier.attachments.length === this.attachments.length) {
    savedossier$.subscribe(
      (data) => console.log('Dossier saved!'),
      (err) => console.error('err: ' + err)
    );
  }
}

const savedossier$ = this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);

这个带有比较数组长度的 if 语句的解决方案有效。但我认为这不是推荐的方式。

rxjs有没有更好的解决方案?

您可以使用 switchMap and tap 运算符:

const fileAndSaveDossier$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
  tap(data => this.dossier.attachments.push(data)),
  switchMap(() => this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier)),
);

fileAndSaveDossier$.subscribe(
  (data) => console.log('Dossier saved!'),
  (err) => console.error('err: ' + err)
);

一个很好的视频解释 switchMap()https://www.youtube.com/watch?v=6lKoLwGlglE

我也不确定你为什么要使用 mergeMap()。 您可以按照以下方式进行操作:

const fileAndSaveDossier$ = this.http.post<Attachement>(
  `${this.addAttUrl}`, this.attachments
).pipe(
  tap(data => this.dossier.attachments.push(data)),
  switchMap(() => this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier)),
);

如果我没理解错的话,你需要先完成所有的fileupload操作才能保存文档,里面必须包含所有的ids 由 fileupload 操作返回。

我还假设 this.attachments 是某种包含附件名称的数组。

如果这一切都是真的,我会这样做。

首先,我将创建一个 Observable 数组,每个代表您要进行的 http 调用以保存附件并接收其 id 作为响应

const saveAttachments = this.attachments).map(
  file => this.http.post<Attachement>(`${this.addAttUrl}`, file),
);

然后您可以使用 forkJoin 像这样

并行执行所有这些 observable
forkJoin(saveAttachments)

其中 returns 一个 Observable,它在 saveAttachments 数组中作为参数传递的所有 observable 完成时发出。发出的值是一个数组,其中包含返回的所有 id

现在您可以执行最后一个操作,即保存档案,将 savedossier$ 可观察到的与上述 forkJoin 返回的连接起来。最终代码如下所示

const saveAttachments = this.attachments).map(
  file => this.http.post<Attachement>(`${this.addAttUrl}`, file),
);
forkJoin(saveAttachment).pipe(
   concatMap(ids => {
      this.dossier.attachments.push(data);
      return this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);
   })
)

请注意,您必须使用 concatMap 运算符来指定您要执行作为参数传递给 concatMap after[=49= 的函数返回的可观察对象] 之前的可观察对象,即 forkJoin 返回的对象,已经完成。

如果您想了解有关如何在不同用例中使用 Observables 组合 http 调用的一些模式,this article may be of interest