import { Injectable } from '@angular/core';
import {
  Observable,
  Subject,
  BehaviorSubject,
  ReplaySubject,
  of,
  from,
  interval,
  timer,
  forkJoin,
  combineLatest,
  throwError,
} from 'rxjs';
import {
  map,
  filter,
  tap,
  take,
  takeUntil,
  debounceTime,
  distinctUntilChanged,
  switchMap,
  mergeMap,
  catchError,
  delay,
  scan,
} from 'rxjs/operators';

// ─────────────────────────────────────────────────────────────
// 第1节:Observable 基础
// Observable 是"懒"的 —— 不订阅就不执行
// ─────────────────────────────────────────────────────────────

export function demo01_observable_basics(): void {
  console.group('【1】Observable 基础');

  // 用 of() 创建一个同步 Observable
  const nums$ = of(1, 2, 3, 4, 5);

  // subscribe 才会真正执行
  nums$.subscribe({
    next: (v) => console.log('收到值:', v),
    error: (e) => console.error('错误:', e),
    complete: () => console.log('完成'),
  });

  // 用 from() 把数组/Promise 转成 Observable
  const arr$ = from(['apple', 'banana', 'cherry']);
  arr$.subscribe((v) => console.log('水果:', v));

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第2节:pipe + 基础 operators
// pipe 是"管道",数据依次经过每个 operator
// ─────────────────────────────────────────────────────────────

export function demo02_pipe_operators(): void {
  console.group('【2】pipe + 基础 operators');

  of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    .pipe(
      tap((v) => console.log('  tap原始值:', v)),   // 只看,不改变
      filter((v) => v % 2 === 0),                    // 只保留偶数
      map((v) => v * 100),                           // 乘以100
      take(3),                                        // 只取前3个
    )
    .subscribe((v) => console.log('最终结果:', v));
  // 输出: 200, 400, 600

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第3节:Subject —— 可以手动推送值的 Observable
// ─────────────────────────────────────────────────────────────

export function demo03_subject(): void {
  console.group('【3】Subject');

  const subject$ = new Subject<string>();

  // 可以有多个订阅者(多播)
  subject$.subscribe((v) => console.log('订阅者A:', v));
  subject$.subscribe((v) => console.log('订阅者B:', v));

  // 手动推送值
  subject$.next('hello');
  subject$.next('world');

  // 晚来的订阅者什么都收不到
  subject$.subscribe((v) => console.log('订阅者C(晚到):', v));
  subject$.next('late message');  // C 能收到,但前两条错过了

  subject$.complete();

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第4节:BehaviorSubject —— 记住最后一个值
// Angular Service 跨组件共享状态的标准做法
// ─────────────────────────────────────────────────────────────

@Injectable({ providedIn: 'root' })
export class UserStateService {
  // 私有,外部不能直接 next()
  private _userName$ = new BehaviorSubject<string>('游客');

  // 公开只读视图
  readonly userName$ = this._userName$.asObservable();

  // 读当前值(不需要订阅)
  get currentUserName(): string {
    return this._userName$.getValue();
  }

  login(name: string): void {
    this._userName$.next(name);
  }

  logout(): void {
    this._userName$.next('游客');
  }
}

export function demo04_behavior_subject(): void {
  console.group('【4】BehaviorSubject');

  const count$ = new BehaviorSubject<number>(0);

  // 新订阅者立即能拿到当前值(0)
  count$.subscribe((v) => console.log('计数:', v));

  count$.next(1);
  count$.next(2);

  // 这个晚来的订阅者能立即收到当前值 2
  count$.subscribe((v) => console.log('晚来的订阅者收到:', v));

  count$.next(3);

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第5节:ReplaySubject —— 重放最近N条记录
// ─────────────────────────────────────────────────────────────

export function demo05_replay_subject(): void {
  console.group('【5】ReplaySubject');

  // 缓冲最近3条
  const replay$ = new ReplaySubject<number>(3);

  replay$.next(1);
  replay$.next(2);
  replay$.next(3);
  replay$.next(4);
  replay$.next(5);

  // 晚来的订阅者能收到最近3条:3, 4, 5
  replay$.subscribe((v) => console.log('重放值:', v));

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第6节:switchMap —— 切换流,取消旧请求
// 最典型场景:搜索框输入,只关心最新一次的结果
// ─────────────────────────────────────────────────────────────

function fakeHttpSearch(keyword: string): Observable<string[]> {
  console.log(`  → 发起请求: "${keyword}"`);
  return of([`${keyword}_结果1`, `${keyword}_结果2`]).pipe(
    delay(300),
    tap(() => console.log(`  ← 收到响应: "${keyword}"`)),
  );
}

export function demo06_switchMap(): void {
  console.group('【6】switchMap(搜索防抖)');

  const searchInput$ = new Subject<string>();

  searchInput$
    .pipe(
      debounceTime(200),           // 200ms 内只取最后一次输入
      distinctUntilChanged(),       // 值没变就不发
      switchMap((kw) => fakeHttpSearch(kw)),  // 切换到新的 HTTP 流,自动取消旧的
    )
    .subscribe((results) => console.log('搜索结果:', results));

  // 模拟快速输入
  searchInput$.next('a');
  searchInput$.next('an');
  searchInput$.next('ang');      // 前两个被 debounce 丢弃
  setTimeout(() => searchInput$.next('angu'), 300);
  setTimeout(() => searchInput$.next('angul'), 600);

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第7节:mergeMap —— 并行执行多个流
// 场景:同时发多个 HTTP 请求,不互相等待
// ─────────────────────────────────────────────────────────────

function fakeHttpGetUser(id: number): Observable<{ id: number; name: string }> {
  return of({ id, name: `User_${id}` }).pipe(delay(100));
}

export function demo07_merge_map(): void {
  console.group('【7】mergeMap(并行请求)');

  from([1, 2, 3])
    .pipe(
      mergeMap((id) => fakeHttpGetUser(id)),  // 3个请求同时发出
    )
    .subscribe((user) => console.log('用户:', user));

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第8节:forkJoin —— 等所有请求都完成
// 场景:页面初始化时需要多个接口都返回才能渲染
// ─────────────────────────────────────────────────────────────

export function demo08_fork_join(): void {
  console.group('【8】forkJoin(等所有完成)');

  forkJoin({
    users: fakeHttpGetUser(1),
    config: of({ theme: 'dark' }).pipe(delay(200)),
    permissions: of(['read', 'write']).pipe(delay(150)),
  }).subscribe((result) => {
    console.log('所有数据加载完成:', result);
    // { users: {id:1, name:'User_1'}, config: {...}, permissions: [...] }
  });

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第9节:combineLatest —— 多个流,任一变化就合并最新值
// 场景:表单有多个字段,任意字段变化时重新计算结果
// ─────────────────────────────────────────────────────────────

export function demo09_combine_latest(): void {
  console.group('【9】combineLatest(多流合并)');

  const price$ = new BehaviorSubject<number>(100);
  const quantity$ = new BehaviorSubject<number>(1);
  const discount$ = new BehaviorSubject<number>(0);

  combineLatest([price$, quantity$, discount$])
    .pipe(
      map(([price, qty, disc]) => price * qty * (1 - disc / 100)),
    )
    .subscribe((total) => console.log('总价:', total));

  price$.next(200);      // → 200 * 1 * 1 = 200
  quantity$.next(3);     // → 200 * 3 * 1 = 600
  discount$.next(10);    // → 200 * 3 * 0.9 = 540

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第10节:catchError —— 错误处理
// ─────────────────────────────────────────────────────────────

export function demo10_error_handling(): void {
  console.group('【10】catchError(错误处理)');

  throwError(() => new Error('网络超时'))
    .pipe(
      catchError((err) => {
        console.error('捕获到错误:', err.message);
        return of('使用默认值继续');  // 用默认值替代,流不中断
      }),
    )
    .subscribe((v) => console.log('结果:', v));

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第11节:scan —— 类似 reduce,但每次都 emit 中间值
// 场景:累加器、购物车数量、消息列表追加
// ─────────────────────────────────────────────────────────────

export function demo11_scan(): void {
  console.group('【11】scan(累加状态)');

  const action$ = new Subject<number>();

  action$
    .pipe(scan((acc, curr) => acc + curr, 0))
    .subscribe((total) => console.log('累计:', total));

  action$.next(10);   // 10
  action$.next(5);    // 15
  action$.next(-3);   // 12

  console.groupEnd();
}

// ─────────────────────────────────────────────────────────────
// 第12节:interval + takeUntil —— 定时器 + 手动停止
// ─────────────────────────────────────────────────────────────

export function demo12_interval_takeuntil(): void {
  console.group('【12】interval + takeUntil');

  const stop$ = new Subject<void>();

  interval(500)          // 每500ms发一个值
    .pipe(
      take(10),           // 最多10个
      takeUntil(stop$),   // 或者 stop$ 发出时停止
      map((i) => `第${i + 1}次`),
    )
    .subscribe({
      next: (v) => console.log(v),
      complete: () => console.log('定时器已停止'),
    });

  // 2秒后手动停止
  setTimeout(() => stop$.next(), 2000);

  console.groupEnd();
}

 

posted on 2026-07-24 18:19  夜之独行者  阅读(4)  评论(0)    收藏  举报