Skip to content

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

Open

Description

@phillipwildhirt

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.

Activity

  1. macksal commented on Apr 14, 2025

    @macksal

    Your reproduction does not seem to give the output you provided.

    You may want to know that the output observable is cold and does not subscribe to the ReplaySubject until you call subscribe on it. So you are missing every emission prior to calling subscribe. After you subscribe to the output, only a single value from the ReplaySubject is replayed, and so nothing is emitted from the scan operator.

    Moving subscribe before the view$.next calls makes this work as expected on 7.8.1

    Suggest closing

    Edit: keep in mind you are also mutating the accumulator array with push instead of copying it inside scan, so if you are retaining this array elsewhere in the program or mutating it further that is probably why you are seeing an empty array in your logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions