首页 > 解决方案 > RXJS 如何在成功处理错误的同时加载图像数组?

问题描述

我正在寻找预加载一堆图像,并且已经打折base64createObjectURL所以我会采取更好的选择,但这就是我所拥有的。

无论如何,这就是我正在看的,一个执行此操作的函数。将一组 URLS 加载为图像。

const urls = ["lol.jpg"];
const images = urls.map((url) => {
    const imageElement = document.createElement('img');
    const imageComplete = fromEvent(imageElement, 'load');
    imageElement.src = targetURL;
    return imageComplete;
});
forkJoin(images)

但是我如何在这里正确处理加载错误?我添加了一个新的fromEvent,但现在我有 2 个事件,而我以前只有一个,还有一个是special error case.

const urls = ["lol.jpg"];
const images = urls.map((url) => {
    const imageElement = document.createElement('img');
    const imageComplete = fromEvent(imageElement, 'load');
    const imageError = fromEvent(imageElement, 'error');
    imageElement.src = targetURL;
    return imageComplete; // <--- not good enough now
});
forkJoin(images)

在这里监听错误是否正确?最终,我需要知道其中是否有任何失败并认为它们都是失败的,但在我的测试中,404 没有catchError任何问题,这让我想到了这个问题。

标签: rxjs

解决方案


答案部分取决于fromEvent(imageElement, 'error')这里的作用:

fromEvent(imageElement, 'error').subscribe({
  next: val => console.log("next: ", val),
  error: err => console.log("error:", err)
});

如果您这样做,并且您收到一个错误,事件是触发next还是error?无论哪种方式,我假设您想imageElement在加载失败时将其删除。

如果它触发error,那么你可以这样做:

const imageComplete = fromEvent(imageElement, 'load');
const imageError = fromEvent(imageElement, 'error').pipe(
  catchError(err => {
    imageElement.remove();
    return throwError(err);
  })
);
imageElement.src = targetURL;
return merge(imageComplete, imageError);

如果它触发next,那么您可以通过抛出如下错误将 imageError 切换到出错的流中:

const imageComplete = fromEvent(imageElement, 'load');
const imageError = fromEvent(imageElement, 'error').pipe(
  tap(x => {
    imageElement.remove();
    throw(new Error(x.message));
  })
);
imageElement.src = targetURL;
return merge(imageComplete, imageError);

现在,forkJoin如果它的任何来源失败,它将因您的错误而失败。如果您不希望这样,则需要在错误到达您的 forkJoin 之前对其进行处理。


重试失败的图像几次

这里最棘手的一点是,您需要创建一个图像元素作为流的一部分,这样当您重试时,它不仅会重新订阅已经失败的元素的事件。

另一个棘手的问题是,您希望创建 imageElement 作为订阅流的一部分,而不是作为其定义的一部分。这defer基本上是为你完成的。

有了这个,没有太多的变化,你可以很简单地重新尝试加载你的图像。retryWhen如果你想让这个过程更复杂一些,你可以继续阅读(比如在重试之前延迟一点)。

const images = urls.map(targetURL =>
  defer(() => of(document.createElement('img'))).pipe(
    mergeMap(imageElement => {
      const imageComplete = fromEvent(imageElement, 'load');
      const imageError = fromEvent(imageElement, 'error').pipe(
        catchError(err => {
          imageElement.remove();
          return throwError(err);
        })
      );
      imageElement.src = targetURL;
      return merge(imageComplete, imageError);
    }),
    retry(5),
    catchError(err => {
      console.log(`Failed to load img (${targetURL}) 5 times`);
      // Throw error if you want forkJoin to fail, emit anything else
      // if you want only this image to fail and the rest to keep going
      return of(new DummyImage());
    })
  )
);
forkJoin(images);

在这种情况下,由于我们在错误命中之前处理了错误forkJoin,我们可以预期图像数组包含DummyImage图像加载失败 5 次的任何位置。你可以做DummyImage任何事,真的。例如,在本地加载低分辨率默认 img。

如果你return throwError(err)改为,那么当任何图像无法加载 5 次时,整个过程forkJoin都会失败(尽管它仍会重试 5 次)并且你不会得到任何图像。这可能是你想要的?


推荐阅读