You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxJS技术问询:无需Observable完成使用forkJoin及复用已完成Observable

RxJS forkJoin 问题解决方案

问题解答

1. 能否在底层Observable未完成时触发forkJoin()?

不行。forkJoin的核心设计逻辑就是等待所有传入的Observable都完成,再收集每个Observable的最后一个值。只要有一个源Observable未完成,forkJoin就不会发射任何值,这是它的固有特性,无法绕过。

2. 能否复用已完成的Observable?

可以,但针对你需要重复点击发起请求的场景,更合理的方式是重新设计流的结构,避免依赖已完成的Observable。通过将forkJoin嵌套在点击事件流内部,每次点击都重新执行一次forkJoin,既满足“等待所有请求完成再更新UI”的需求,又支持重复触发。

修改后的代码

JavaScript 部分

import { catchError, exhaustMap, forkJoin, fromEvent, of, tap } from 'rxjs';
import { ajax } from "rxjs/ajax";
import { map } from "rxjs/operators";

const observer = {
    next: val => addImages({ dog: val.dog, cat: val.cat }),
    error: err => console.log('error handled in observer'),
    complete: () => console.log('complete!')
};

const fetchButton = document.querySelector('.fetchImages');

// 点击事件触发,每次点击都发起一组新的请求组合
fromEvent(fetchButton, 'click').pipe(
    exhaustMap(() => 
        forkJoin({
            dog: ajax.getJSON('http://localhost:3000/dog').pipe(
                map(item => item.message),
                catchError(error => {
                    console.log('error, handling DOG error locally');
                    return of('https://world.openfoodfacts.org/images/products/899/110/278/9318/front_en.9.full.jpg');
                })
            ),
            cat: ajax.getJSON('https://some-random-api.ml/img/cat').pipe(
                map(item => item.link),
                catchError(error => {
                    console.log('error, handling CAT error locally');
                    return of('https://world.openfoodfacts.org/images/products/930/065/703/2029/front_en.3.full.jpg');
                })
            )
        })
    ),
    tap(console.log)
).subscribe(observer);

function addImages({ dog, cat }) {
    document.querySelector('[alt="dog"]').setAttribute('src', dog);
    document.querySelector('[alt="cat"]').setAttribute('src', cat);
}

HTML 部分(无需改动)

<section>
    <button class="fetchImages">Fetch!</button>

    <div class="images">
        <img alt="dog">
        <img alt="cat">
    </div>
</section>

关键改动说明

  • 移除了独立的dogSource$、catSource$等流,直接将点击事件作为触发源,每次点击通过exhaustMap发起新的forkJoin请求组合,避免了原代码中take(1)导致流完成后无法重复触发的问题。
  • exhaustMap会忽略当前请求未完成时的重复点击,防止重复发起请求,保留了你原代码中的需求逻辑。
  • 错误处理和数据映射逻辑内嵌在forkJoin的源Observable中,结构更清晰,且每次点击都会重新执行,不会因流完成而失效。

如果确实需要复用已完成Observable的结果(比如缓存请求数据),可以使用shareReplay(1)操作符缓存最后一个值,示例如下:

const cachedDogImage$ = ajax.getJSON('http://localhost:3000/dog').pipe(
    map(item => item.message),
    catchError(error => {
        console.log('error, handling DOG error locally');
        return of('https://world.openfoodfacts.org/images/products/899/110/278/9318/front_en.9.full.jpg');
    }),
    shareReplay(1)
);

但该方案更适合缓存场景,对于你每次点击需要新数据的需求,嵌套forkJoin的方案更贴合。

内容的提问来源于stack exchange,提问作者thomasquestions

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 13:05:23