自定义RxJS操作员无法正常工作

时间:2018-02-12 08:20:06

标签: angular typescript rxjs

我一直在尝试将一段代码重构为自定义rxjs运算符,但我无法让它工作。

到目前为止,这是我的自定义运算符:

dtype=object

这里的想法是将新列表与旧列表进行比较,如果两个列表中的最后一项匹配,则export const isLastItemTheSame = (oldValue: any[], key: string, condition: boolean) => { return condition ? <T>(obsv: Observable<T[]>) => obsv.pipe( filter(newValue => { try { return (oldValue[oldValue.length - 1][key] === newValue[newValue.length - 1][key]); } catch(err) { return false; } }), mapTo(true) ) : <T>(obsv: Observable<T>) => obsv.pipe(ignoreElements()); }; 的成功回调不应触发。但是如果这些项目不匹配,那就应该。

我现在遇到的问题是:

  • subscribe位不起作用,因为它没有触发成功回调。
  • <T>(obsv: Observable<T>) => obsv.pipe(ignoreElements())condition时,运算符返回布尔值而不是新列表。这使得无法将新列表绑定到订阅成功回调中的true

我用它像:

this.items

已经注释掉的代码是我尝试重构的代码,这使我很容易看到我想要实现的目标。

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

编辑:该答案假定您实际上要与外部引用列表进行比较,而不仅仅是同一个可观察值的先前发出值进行比较。这就是您的问题所要说明的内容,但是如果您要引用先前的值,请查看my second answer


让我们先进行一些观察:

  

当条件为true时,运算符将返回布尔值而不是新列表。

这就是您的mapTo(true)的工作,因此,请忽略它:-)

  

ignoreElements())位不起作用,因为它不会触发成功回调。

那是因为您不想忽略这些元素,而是在这种情况下只需返回可观察的值即可。

但是,我不会将条件移到运算符中-从逻辑上讲,它与它无关,您无论如何都要将整个逻辑(if的“主体”)传递给运算符。

此外,您的运算符当前缺乏适当的类型签名,并且创建自定义运算符的操作反而不太繁琐,例如使用静态pipe函数(不过我们在这里甚至不需要)。

最后,如果您希望对通过的每个值进行重新评估,我们不能仅仅静态地传递参考列表,因此我们需要使用供应商。


因此,让我们修复所有这些问题。我对运营商的建议是这样:

import { OperatorFunction } from 'rxjs';
import { filter } from 'rxjs/operators';

export function filterIfLastElementMatches<T extends Record<any, any>>(
  previousSupplier: () => T[], 
  key: keyof T
): OperatorFunction<T[], T[]> {
  return filter(value => {
    const previous = previousSupplier();
    return !previous.length || !value.length || value[value.length - 1][key] !== previous[previous.length - 1][key]
  });
}

然后使用它并将其与条件部分结合起来

source$.pipe(
  // …
  !isReset ? filterIfLastElementMatches(() => this.items, 'eventid') : tap(),
).subscribe(items => { /* … */ });

查看实际情况here

请注意,这里条件的语义略有不同,因为它将在执行包装方法时(而不是在发出值出现时)进行评估。我认为这在这里并不重要。

答案 1 :(得分:1)

注意:这是第二个答案,但是这次我们假设您要与之前发出的值进行比较,而不是与外部参考列表进行比较。 my other answer中的一般要点仍然适用。

如果这是您需要的机制,我强烈建议您使用此答案,而不是使用外部供应商。


如果要与之进行比较的值应该只是先前发出的值,则可以通过自定义比较器函数对此简单地使用distinctUntilChanged。如果您坚持将其包装到自定义运算符中,则可能是这样:

import { OperatorFunction } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

export function distinctUntilLastElementByKey<T extends Record<any, any>>(
    key: keyof T
): OperatorFunction<T[], T[]> {
    return distinctUntilChanged((previous, value) =>
        previous.length && value.length && value[value.length - 1][key] === previous[previous.length - 1][key]
    );
}

您可以在操作here中看到它。

但是,仅将比较器函数重构为它自己的函数并在代码中仅使用

source$.pipe(distinctUntilChanged(createLastItemKeyComparator('eventid')))

答案 2 :(得分:0)

如何:我的意思是-作为骨架-您可以将关键测试扩展到您的需求。

const filterIfLastEqual = () => <T>(source: Observable<T[]>) => {
  return new Observable(observer => {
    let lastValue: T;

    return source.subscribe({
      next(x) {
        const [nowLast] = x.slice(-1);
        if (lastValue !== nowLast) {
          console.log('diff', lastValue, nowLast)
          observer.next(x);
          lastValue = nowLast;
        } else {
          console.log('skipping', lastValue, nowLast)
        }
      },
      error(err) {
        observer.error(err);
      },
      complete() {
        observer.complete();
      }
    })

  })
}

const emitter = new Subject<any[]>();
emitter.pipe(
  filterIfLastEqual(),
).subscribe(
  v => console.log('received value:', v)
)

// test cases
emitter.next([1, 2, 3, 4]);
emitter.next([1, 2, 3, 4]);
emitter.next([1, 2, 3, 5]);
emitter.next([1, 2, 3, '5']);

您还可以结合使用scan()(它是有状态的)和distnctUntilChanged()。逻辑几乎相同。

相关问题