Exploring the depths of Rx.ReplaySubject: Techniques for delaying the `next()` event

Confused Mind: Either I'm mistaken, or the whiskey is starting to take effect. (I can't rule out the possibility that I'm just going crazy. Apologies for that.)

Assumption:

My assumption was that ReplaySubject would emit a single value every 2 seconds because I wait two seconds before calling next() each time.

Outcome:

However, all values are actually output simultaneously after waiting for 2 seconds.

The problematic code snippet is as follows:

import { ReplaySubject } from 'rxjs';

export const rs$: ReplaySubject<number> = new ReplaySubject();

rs$.subscribe({
  next: (data) => console.log(data),
  error: (error) => console.warn(error),
  complete: () => console.log('ReplaySubject completed'),
});

const fakeAPIValuesOne: Array<number> = [7, 11, 13];

fakeAPIValuesOne.forEach(async (entry: number) => {
  await wait(2000);  // <--- Why does wait() not work?
  rs$.next(entry);
});

function wait(milliseconds: number) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

Query:

What am I fundamentally misunderstanding in this scenario?

If you want to test it yourself: https://stackblitz.com/edit/rxjs-wpnqvq?file=index.ts

UPDATE 1:

Using setTimeout also doesn't seem to make any difference. The following modified code results in the same behavior as before:

fakeAPIValuesOne.forEach((value: number) => {
  setTimeout(() => {
    rs$.next(value);
  }, 2000);
});

I'm puzzled by how next() manages to override all the delays here?

UPDATE 2

The issue has been resolved, correct answer identified, thank you! You need this setup to enable root level awaits for your ts-files.

package.json

Please pay attention to the type section:

{
  "name": "playground",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "start": "nodemon index.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "rxjs": "^7.5.5",
    "ts-node": "^10.7.0",
    "typescript": "^4.8.0-dev.20220507"
  },
  "type": "module"
}

nodemon.json

Please use the following configuration to avoid the error:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" 

{
  "execMap": {
    "ts": "node --loader ts-node/esm"
  }
}

Lastly, update your tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "isolatedModules": true,
    "noEmit": true,
    "strict": true,
    "lib": ["esnext", "DOM"]
  }
}

Answer №1

Extracted from the Mozilla Web Docs, the following note provides important information regarding the use of forEach:

Note: When utilizing forEach, it is crucial to remember that it expects a synchronous function.

forEach does not wait for promises to resolve. It is essential to understand the potential implications when incorporating promises (or async functions) within the callback of forEach.

It must be noted that the issue at hand does not pertain to the ReplaySubject; rather, it signifies that forEach is incompatible with this particular scenario.

Cheers

EDIT: Issue Resolved

import { ReplaySubject } from "rxjs";

export const rs$ = new ReplaySubject();

rs$.subscribe({
  next: (data) => console.log(data),
  error: (error) => console.warn(error),
  complete: () => console.log("ReplaySubject completed"),
});

const fakeAPIValuesOne = [7, 11, 13];

// This approach will not work:
// fakeAPIValuesOne.forEach(async (entry: number) => {
//   await wait(2000);
//   rs$.next(entry);
// });


// However, the following method will succeed
for (const element of fakeAPIValuesOne) {
  await wait(2000);
  rs$.next(element);
}

function wait(milliseconds: number) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

Answer №2

It is important to note that expecting this code to work as intended may not always be realistic.

Take a look at the following example:

wait(2000);
wait(2000);
wait(2000);
console.log("Hello");

The message "Hello" will be printed immediately, without waiting for the total 6 seconds.

This behavior remains unchanged even when placed in a loop.

for (const n of [1,2,3]) {
  wait(2000);
}
console.log("Hello");

In this case, "Hello" will also be printed right away.


If you do not utilize .then() or await, the standard prediction is that the program will execute without any delays. Some APIs may automatically handle waiting, but it should not be assumed.

You must structure your code like this:

await wait(2000);
await wait(2000);
await wait(2000);
console.log("Hello");

Now there will be a 6-second delay before "Hello" is displayed on the console.


Consider the following scenario:

const createPromise = async (v) => {
   await wait(2000);
   console.log("Hello From createPromise: ", v);
}

createPromise(1);
createPromise(2);
createPromise(3);
console.log("Hello");

You will observe that the output remains instantaneous, followed by all promises resolving after a 2-second interval.

Hello
// 2 seconds wait
Hello From createPromise: 1
Hello From createPromise: 2
Hello From createPromise: 3

Notably, none of them pause for one another. This aligns with the expected behavior of promises. To ensure waiting for promise results, incorporate await or .then.

The correct approach would be:

await createPromise(1);
await createPromise(2);
await createPromise(3);
console.log("Hello");

Both loops yield similar outcomes, as anticipated - they do not introduce any delays.

for (const n of [1,2,3]) {
  createPromise(n);
}

[1,2,3].forEach(n => createPromise(n))

console.log("Hello");

Once again, the initial output will be "Hello" since no awaiting is implemented for the promise resolutions.

While the inner wait(2000) promise is awaited, the outer createPromise() promises are not, hence there is no delay.

for (const n of [1,2,3]) {
  await createPromise(n);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieve a single value from a JavaScript array

There must be something simple I am missing here, as all the search results I found relate to looping over arrays, which is not what I want. My ajax call returns a response in the form of a straightforward array. When I use console.log(response); in the s ...

What could be the reason for the discrepancy between the values displayed in the two alerts? What is causing this difference in

Check out this JavaScript code snippet: var x = 3; var foo = { x: 2, baz: { x: 1, bar: function() { return this.x; } } } var go = foo.baz.bar; alert(go()); alert(foo.baz.bar()); When you run the two alert functions here, you&ap ...

Javascript Rest for y moments

My website's JavaScript function uses AJAX to retrieve account information and open a modal for viewing and editing. Sometimes, the details don't update quickly enough in the database before the function collects them again, leading to discrepanc ...

Is there a way to search for the keyword "/test/" within a lengthy string using Regular Expressions?

When faced with a string structured like this: const myString = /example/category/modify?itemID=someID&type=number How can I efficiently extract the segment "/category/" by employing: const subSegment = myString.match(...); My framework of choice i ...

Updating the chosen option using jQuery

Is there a way to change the currently selected option using jQuery? <select name="nameSelect" id="idSelect"> <option value="0">Text0</option> <option value="1">Text1</option> <option value="2" selected>Text ...

Require that the parent FormGroup is marked as invalid unless all nested FormGroups are deemed valid - Implementing a custom

Currently, I am working on an Angular 7 project that involves dynamically generating forms. The structure consists of a parent FormGroup with nested FormGroups of different types. My goal is to have the parentForm marked as invalid until all of the nested ...

Enhance the appearance of Ionic popups

Can someone help me with resizing a pop up? I've been struggling to get it right. This is the popup template in question: <ion-view> <ion-content scroll="false" class=""> test </ion-content> < ...

Issue with displaying errors in vee-validate when using Vue.js Axios (More details provided)

When working on my Vue project, I encountered an issue while using Vee-Validate and Axios. During an API call to register a user, if the email is already in use, an error is thrown. To handle this error and display the error message, I used the following ...

Tips for Transferring Values Between Multiple Dropdown Menus with jQuery

Hello there, can anyone guide me on how to transfer selected items from one multiple combo box to another multi-combo box? I would really appreciate it if someone could provide an example for this scenario. <HTML> <HEAD> <TITLE></T ...

In React, when a user clicks the back button on the browser window, ensure you pass props back to the originating component

While moving from component A to component B through routing, I want to send a prop back to A when the user clicks on the browser's back button while on B. Can this be achieved? I am currently using React's history.push for navigating between com ...

Unable to retrieve jwt token from cookies

Currently, I am developing a website using the MERN stack and implementing JWT for authentication. My goal is to store JWT tokens in cookies. Despite invoking the res.cookie function with specified parameters (refer to the code below), I am facing difficul ...

Incorporate a new CSS class into a DIV using JavaScript

Sample HTML: <div id="bar" class="style_one"></div> Is there a way to include the class style_two without deleting style_one? final outcome: <div id="bar" class="style_one style_two"></div> ...

What is the best way to implement Bootstrap 5 jQuery plugins in an ES6 project with Webpack?

Currently in the process of transitioning an ES6 project from Bootstrap 4 to Bootstrap 5, encountering the following error: Error: Uncaught TypeError: bootstrapElement.Tooltip is not a function According to the Migration Notes, Bootstrap 5 no longer inc ...

Ways to store data in the localStorage directly from a server

I'm facing an issue - how can I store data in localStorage that was received from the server? Should I use localStorage.setItem for this purpose? And how do I handle storing an array in localStorage? Or am I missing something here? import { HttpCli ...

Is it possible to close the navigation menu by clicking on a link or outside of the navigation area?

Hey everyone, I've recently dived into the world of web design and encountered my first hurdle. I need your expertise to help me solve this problem. How can I modify my JavaScript to close the NAV element by clicking on one of the links or outside t ...

Utilizing the Jquery click function to assign an element as a variable

I'm currently working on a script that aims to extract the inner text of any clicked item with the class "customclass". Keep in mind that this specifically targets anchor elements. However, I've encountered an issue where the individual element ...

Having difficulty modifying the styling of a paragraph within a div container

I have been working on a function that is supposed to adjust the font-size and text-align properties of a paragraph located within a div tag once a button is pressed. function customizeText() { document.getElementById('centretext').innerHTML = ...

Include the name of the uploaded attachment in the textarea before hitting the submit button

Is there a way to automatically insert the filename into a textarea before the user submits the form? Can this be achieved using PHP or JavaScript? Thank you. <form id="Apply" name="Apply" method="post" enctype="multipart/form-data" action="applyLea ...

Updating a component (`AddBudgetModal`) while rendering a different component (`App`) is not possible. To identify the incorrect setState() call within the `AddBudgetModal` component

Here is the code snippet from App.jsx I'm facing an issue on line 44 where I'm attempting to open an addbudgetmodal by passing true or false. However, I'm getting an error message that says "Cannot update a component (App) while rendering a ...

Pass information captured from Mat Dialog up to the main component

Looking for a way to pass true/false boolean data from a dialog box into the parent component without just console logging the result? You want to store it in a variable in the parent component for further use. Any suggestions on how to achieve this? This ...