我正在使用Angular开发一个应用程序,我正在try 构建一个包含多个观察值(发出多个值)和HTTP请求(发出一个值)的流.

我想打开一个InApp浏览器,让用户进行交易(付款)并读取回调,这是浏览器将try 加载的新url.我需要订阅浏览器的多个事件,以便在出现错误时获得通知、加载新的url等..

我想正确地处理这个RxJS组合,如果满足某些条件以保证性能且没有潜在的内存泄漏,我会取消所有的可观察值.

我试着遵循:

  1. RxJS switchMap does not cancel inner merged observable
  2. Using SwitchMap() to Handle Canceling Previous Requests
  3. RxJS: switchMap inside merge doesn't work to make a conditional observable return

All the following observables and request are fake to avoid complexity.

Observables that emit one value:

getOrderId(): Observable<any> {
  return this.http.get<any>(
    'https://jsonplaceholder.typicode.com/posts/1'
  );
}

notifyBackend(url: string): Observable<any> {
  return this.http.get<any>(
    'https://jsonplaceholder.typicode.com/posts/1'
  ).pipe(delay(2000));
}

Observables that emit multiple values:

Each one will emit at any time, the 100 property will tell the event type and what had happened.

getMultipleOne(): Observable<any> {
  return of({type: 'exit'}).interval(3000);
}

getMultipleTwo(): Observable<any> {
  return of({type: 'loaderror', url: 'http://www.blabla.com'}).interval(2000);
}

getMultipleThree(): Observable<any> {
  // The url returned can be any, I'm waiting for mine.
  return of({type: 'loadstart', url: 'http://www.google.com'}).interval(1000);
}

我试图实现的流程是:

  1. 调用发出一个值并切换到另一个可观察值的getOrderId
  2. 同时听三个可观察的多重值.我会对这些事件做出react ,并做一些商业逻辑.
  3. 如果typeexit,取消所有订阅,不通知来电者.表示用户刚刚手动关闭/取消.
  4. 如果typeloadstart,判断url,看看切换到另一个HTTP请求是不是我的,或者全部取消.
  5. 如果是我的url,请调用后端保存结果,并通知调用者显示良好的UI结果.

我试过:

initFlow() {
    return this.getOrderId() // Emit just one value
        .pipe(
            // Switch to another one that is the merge of 3 observables
            switchMap(() => {
                return merge(
                    getMultipleOne(),
                    getMultipleTwo(),
                    getMultipleThree()
                );
            }),
            // This tap will be executed every time the above observables emit something
            tap(event => console.log(event)),
            tap(event => {
                if (event.type === 'exit') {
                    // Cancel all previous observables if this happen?
                    // Since the flow is not completed, the caller must not be notified, I guess?
                }
            }),
            filter(event => event.type === 'loadstart'),
            pluck('url'),
            // If not meet, cancel all above without notifying the caller
            switchMap(url => this.isGoogle(url) ? EMPTY : of (url)),
            // Switch to another HTTP that emits one value or throw error to cancel all
            switchMap(url => {
                if (this.isMyPersonalWebSite(url)) {
                    return this.notifyBackend(url); // Emit just one value
                } else {
                    // Let the caller be notified by its error event to show UI
                    throw new Error('User transaction is failed');
                }
            }),
            // The previous HTTP just emit one value, this allow the caller to receive the completion event.
            // The caller does not need it, but, I guess is the right thing to happen in observables life.
            first(),
        );
}

来电者:

// This one will be unsubscribed later by the component when its onDestroy hook is called
this.myService.initFlow()
    .subscribe({
        next: res => console.log('next', res),
        error: e => console.log('error', e),
        complete: () => console.log('complete')
    });

我的问题:

  1. TODO部分,我不知道如何取消之前的所有内容
  2. 当我返回EMPTY时,我什么也看不到,呼叫者也不在

我的目标是:

  1. 如果满足条件,则取消所有发出多个值的观测值.
  2. notifyBackend()发出时,使用其next事件通知调用者;当我在代码中手动抛出错误时,使用其error事件通知调用者.

如果您有任何关于避免内存泄漏的帮助或建议,以及处理方法,我们将不胜感激.

推荐答案

如果你想完成所有合并的观测值,你需要在siwtchMap()中完成merge()(我没有测试这段代码):

switchMap(() => {
  return merge(
    getMultipleOne(),
    getMultipleTwo(),
    getMultipleThree(),
  ).pipe(
    takeWhile(event => event.type !== 'exit'),
  );
}),
tap(event => {
  if (event === 'loaderror') {
    throw new Error(); // Will be wrapped into `error` notificaiton.
  }
}),
switchMap(event => {
  // At this point `event.type` must be `loadstart`
  if (this.isGoogle(event.url)) {
    return EMPTY;
  }

  if (this.isMyPersonalWebSite(event.url)) {
    return this.notifyBackend(event.url); // Emit just one value
  } else {
    // Let the caller be notified by its error event to show UI
    throw new Error('User transaction is failed');
  }
}),
take(1), // You shouldn't even need to use this

关于你的问题:

  • 要取消所有合并的观测值,需要在switchMap()内完成合并链(我在这里使用takeWhile).

  • 在链中,返回EMPTY,但链以first()结尾,这意味着当this.isGoogle(url) === true时链将完成,first()将发出错误.看这个take(1) vs first().

Angular相关问答推荐

Angular ngModel双向数据绑定不适用于表单中的Select元素

Angular 信号不更新afterNextender()

PrimeNG侧栏清除primeNG消息.为什么?

如何在init为FALSE时以Angular 初始化主拇指擦除器-Swiper 11.0.6Angular 17 SSR

第15角Cookie的单元测试用例

要素存储更改根存储状态

将sass与@Use语句一起使用时出现Angular 新的VITE构建器编译错误

Primeng:安装ANGLE 16.2.0版本

无法绑定到formControl,因为它不是input的已知属性

Angular 16:信号更新未能更新视图

Angular Http 拦截器 - 修改标头会导致 CORS

BehaviorSubject 在 Angular 中制作数据之间的时间表(或计时器)

在 Angular material表上调用 renderRows()

初始化所有子类后的Angular 2生命周期钩子是什么?

Angular 2中基于Condition条件的点击事件

ConnectionBackend 没有提供程序

Angular 2 中的 $implicit 是什么?

Angular 2 中的 OnChanges 和 DoCheck 有什么区别?

Angular 5 中 value 和 ngValue 的区别

Angular2material对话框自动关闭