ReactiveX / ReactiveX/rxjs

scan() operator unexpectedly resets or fails to accumulate emissions across multiple emissions

Open
#7,540 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
31.7k
Forks
3k
PR merge metrics
No merged PRs in 30d

Description

Describe the bug

When using the scan() operator in RxJS (version 7.8.1), the accumulator parameter (a) does not correctly retain previous values across emissions. Instead, it appears to be empty or reset with each new emission, even though the index parameter (i) increments as expected. This suggests that scan() is receiving consecutive emissions but failing to persist the accumulated state.

Expected behavior
  • The scan() operator should correctly accumulate all previous emissions.
  • The accumulator parameter (a) should contain the history of all prior emissions, growing over time.
Index: 1  
Accumulator: []  
Index: 2  
Accumulator: [{ time: 1711010000000, view: [expandedTabs] }]  
Index: 3  
Accumulator: [{ time: 1711010000000, view: [expandedTabs] }, { time: 1711010500000, view: [] }]
Actual Behavior:
  • The accumulator appears empty ([]) on each emission, despite the i index increasing.
  • It behaves as though scan() is reinitializing or not retaining prior emissions properly.
Index: 1  
Accumulator: []  
Index: 2  
Accumulator: []  
Index: 3  
Accumulator: []
Reproduction code
import { ReplaySubject, scan, map, tap, Observable } from 'rxjs';

enum DashboardViews {
  expandedTabs = 'expandedTabs'
}

const views$ = new ReplaySubject<DashboardViews[]>(1);

const viewChange$: Observable<boolean> = views$.pipe(
  map(view => ({ view, time: new Date().valueOf() })),
  scan((a, c, i) => {
    console.log('Index:', i);           // Index increases correctly
    console.log('Accumulator:', a);     // Accumulator is unexpectedly empty on each emission
    a.push(c);
    return a;
  }, [] as { time: number, view: DashboardViews[] }[]),
  tap(v => console.log('Accumulated values:', v)),
  map((views) => {
    if (views.length < 2) return false;
    const curr = views[views.length - 1];
    const prev = views[views.length - 2];
    return prev.view.includes(DashboardViews.expandedTabs) !== curr.view.includes(DashboardViews.expandedTabs);
  })
);

// Simulate emissions
views$.next([DashboardViews.expandedTabs]);
views$.next([]);
views$.next([DashboardViews.expandedTabs]);

viewChange$.subscribe(v => console.log('viewChange', v));
Reproduction URL

No response

Version

7.8.1

Environment
  • RxJS version: 7.81
  • Angular version: 18.2.1
  • Node.js version: 20.18.1
  • Browser: Chrome 133
  • OS: macOS 14.7.4 (23H420)
Additional context
  • The issue persists even when using shareReplay(), defer(), and ReplaySubject() with a larger buffer size, suggesting that this may be a bug in the scan() operator itself.
  • The i index increasing indicates that the emissions are consecutive, yet the accumulator does not persist previous values.
  • This behavior does not align with the expected behavior of scan() in RxJS.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin at the scan() entry point and run the supplied TypeScript reproduction, checking the observable subscription and ReplaySubject setup before comparing accumulator behavior. Confirm the expected accumulation across emissions and use that result to determine whether the operator or reproduction needs attention.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.