Simultaneous asynchronous access to a single object

I have been working with JS/TS for quite some time now, but the concept of race conditions still perplexes me. I am currently attempting to execute the following logic:

class Deployer {
 protected txDataList: Array<TXData>;
 
 constructor() {
   this.txDataList = new Array<TXData>();
 }
 async consumeTXs() {
   if (this.txDataList && this.txDataList.length > 0) {
     // do something
   }
}
setup() {
   // listens for txData events emitted by a service 
  // and pushes them to the txDataList
  this.service.on("txData", async (txData) => {
      this.txDataList.push(txData);
 });
}
start() {
  setup();
  setInterval(this.consumeTXs, 5000);
 }
}

In essence, we are required to listen for events emitted by a certain service, store them in an array, and every 5 seconds process the accumulated events. However, when running this implementation, the consumeTXs function consistently sees an empty list, despite the setup function successfully adding txData events to the array. It seems like there is a discrepancy between the two functions' views of the txDataList object.

Evidently, the current implementation described above is flawed and could potentially result in a race condition. What is the correct approach to implement the desired functionality, and what exactly causes the malfunction in the provided code? I am eager to delve into the underlying mechanics.

Thank you!

Answer №1

The problem you are facing arises from the line

setInterval(this.consumeTXs, 5000);
. When you execute consumeTXs in this way, the context of 'this' becomes the window object instead of your class instance. One possible solution is to use an arrow function to maintain the correct instance context:

setInterval(() => this.consumeTXs(), 5000);

Answer №2

lock/unlock

To enhance the functionality of the Deployer, consider incorporating a "lock" state -

// Deployer.js

class Deployer {
  protected txDataList: Array<TXData>
  protected isLocked: Boolean           // lock state
  constructor() {
    this.txDataList = []
    this.isLocked = false
    setInterval(_ => this.read(), 5000)
  }
  async read() {
    if (this.isLocked) return     // exit if locked
    this.isLocked = true          // lock
    let tx
    while (true) {
      tx = this.txDataList.shift()// fifo tx
      if (tx == null) break       // stop processing if no tx
      await doSomething(tx)       // process each tx
    }
    this.isLocked = false         // unlock
  }
  write(tx: TXData) {
    this.txDataList.push(tx)
  }
}

export default Deployer
// some-service.js

import Deployer from "./Deployer.js"
const deployer = new Deployer()
someService.on("txData", tx => deployer.write(tx))

In this setup, we can write to the deployer at any time, but read only takes place when the deployer is in an "unlocked" state.

buffer

Rather than "lock/unlock," perhaps naming it Buffer would better represent the class behavior. Additionally, abstracting TXData allows us to use our Buffer for any data type. Let's also make the interval a configurable parameter -

// Buffer.js

class Buffer<T> { // generic T
  private buffer: Array<T> // generic T
  private isLocked: Boolean
  constructor({ interval = 5 }) { // adjust interval as needed
    this.buffer = []
    this.isLocked = false
    setInterval(_ => this.read(), interval * 1000)
  }
  async read() {
    if (this.isLocked) return
    this.isLocked = true
    let t
    while (true) {
      t = this.buffer.shift()
      if (t == null) break
      await doSomething(t)
    }
    this.isLocked = false
  }
  write(t: T) {  // generic T
    this.buffer.push(t)
  }
}

Type specification for Buffer<T> becomes Buffer<TXData>. Adjust the interval when creating a new instance of Buffer -

// some-service.js

import Buffer from "./Buffer.js"
const buffer = new Buffer<TXData>({ interval: 10 }) 
someService.on("txData", tx => buffer.write(tx))

move effect to call site

Let's remove the hard-coded doSomething function and provide an onRead parameter to allow the caller to define the action for each item -

// Buffer.js

class Buffer<T> { 
  private buffer: Array<T> 
  private isLocked: Boolean
  private onRead: (x: T) => void                        // onRead
  constructor({ interval = 5, onRead = console.log }) { // onRead
    this.buffer = []
    this.isLocked = false
    setInterval(_ => this.read(), interval * 1000)
  }
  async read() {
    if (this.isLocked) return
    this.isLocked = true
    let t
    while (true) {
      t = this.buffer.shift()
      if (t == null) break
      await this.onRead(t)                               // onRead
    }
    this.isLocked = false
  }
  write(t: T) {
    this.buffer.push(t)
  }
}

The caller now determines the effect previously handled by doSomething -

// some-service.js

import Buffer from "./Buffer.js"

async function doSomething(tx: TXData) {
  await // process tx ...
}

const buffer = new Buffer<TXData>({
  interval: 10,
  onRead: doSomething    // specify onRead
})

someService.on("txData", tx => buffer.write(tx))

related

For a related abstraction using async generators with a channel, check out this Q&A.

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

developing TypeScript classes in individual files and integrating them into Angular 2 components

We are currently putting together a new App using Angular2 and typescript. Is there a more organized method for defining all the classes and interfaces in separate files and then referencing them within angular2 components? import {Component, OnInit, Pi ...

Set up SystemJS to properly load my Angular 2 component module

Recently, I made the switch from ng1 to ng2. I successfully imported Angular 2 and its modules into my project: <script src="/node_modules/systemjs/dist/system.src.js"></script> <script src="/node_modules/rxjs/bundles/Rx.js"></script& ...

How can I prevent SweetAlert from automatically focusing when it first opens?

I recently integrated SweetAlert into my project and customized the buttons like this: swal("A wild Pikachu appeared! What do you want to do?", { buttons: { cancel: "Run away!", catch: { text: "Throw Pokéball!", value: "catch", ...

Executing callback in the incorrect context

I'm facing an issue and can't seem to navigate through it. I am setting up a callback from a third party directive, but the callback is not returning with the correct scope. This means that when it reaches my controller, this refers to some other ...

What is the solution for the error message "TypeError: app.use() is seeking a middleware function"?

I am a beginner in Node.js and have encountered an issue in my passport.js or signupLogin.js file with the error message, app.use() requires a middleware function that I am struggling to resolve. I suspect it may be related to the signupLogin route, as th ...

Load the page's content gradually, without needing to wait for all the content to be fully loaded

I am facing an issue with a webpage that displays 10 different items, each of which has a slow loading time. Currently, the entire page waits for all 10 items to fully load before displaying anything. I am interested in finding out if it is feasible to sh ...

Django app with Vue.js integration is throwing a 405 error for Axios PUT request

I have a project in progress for managing inventory on a small scale. One of the functionalities I'm working on involves updating the stock quantity when a product is withdrawn. I've encountered an issue while using Axios and the .put() function, ...

What steps do you need to take in order to transform this individual slideshow script into numerous slideshows

I came across this code online that effectively creates a slideshow. https://css-tricks.com/snippets/jquery/simple-auto-playing-slideshow/ Is there a way to modify the code to support multiple slideshows? I've made several attempts without success ...

Using Websockets in combination with AngularJS and Asp.net

While working on my application that uses AngularJS with Websocket, I encountered a specific issue. The WebSocket communication with the server works fine as users navigate through different pages in Angular. However, when a user decides to refresh the p ...

Node.js encountered an error: Module 'mongoose' not found

C:\Users\Alexa\Desktop\musicapp\Bots\Soul Bot>node bot.js Node.js Error: Missing module 'mongoose' at Function._resolveFilename (module.js:334:11) at Function._load (module.js:279:25) at Module.requir ...

JavaScript obtain scroll position of a child element

I have a setup that looks something like the following: <div> <div id="scrollID" style="height:100px;"> content here </div> </div> <script> document.getElementById("myDIV").addEventListener("touchstart", m ...

What could be causing the malfunction of material-UI Tabs?

My Material UI Tabs seem to have stopped working after a recent update. I suspect it may be related to the react-tap-event-plugin update I installed. Initially, I thought it was due to tab indexing but even using values like value='a', value=&apo ...

NPM is currently malfunctioning and displaying the following error message: npm ERR! 404

When running npm update -g, the following error occurs: npm ERR! code E404 npm ERR! 404 Not found : default-html-example npm ERR! 404 npm ERR! 404 'default-html-example' is not in the npm registry. npm ERR! 404 You should bug the author to publi ...

Error: The function "execute" has not been declared

Hey there! I've created a Discord bot that is meant to check the status of a Minecraft server, but I'm encountering an issue with the embed. It's showing this error: UnhandledPromiseRejectionWarning: ReferenceError: execute is not defined. ...

Exploring the depths of web automation using Python and Selenium, harnessing the power of a while

Currently, I am navigating through various pages on iens website using Selenium and Python. My goal is to click on the "Volgende" button (which means "next" in Dutch) continuously until there are no more pages left by implementing a while loop. Specificall ...

What methods can I leverage in VueJS to redirect users to a different page or URL?

I am new to JavaScript, so please excuse any mistakes in my code. I am working on a form that utilizes AWS SNS to send an email to my company with the data submitted through the form. The issue I'm facing is that there is no feedback for the user afte ...

Unable to construct a node typescript project using solely production dependencies

I am currently working on a Node TypeScript project that utilizes various third-party libraries such as express. To ensure type safety, I typically install the @types/express module as a dev dependency following common instructions. The installation works ...

Error 404 encountered while attempting to access dist/js/login in Node.JS bootstrap

I currently have a web application running on my local machine. Within an HTML file, I am referencing a script src as /node_modules/bootstrap/dist/js/bootstrap.js. I have configured a route in my Routes.js file to handle this request and serve the appropri ...

Encountering an issue when attempting to save an excel file in Angular 8, receiving an error message that states "

When working with angular 8, I encountered an issue while trying to save an excel file. The error message displayed was as follows: ERROR TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed. at Functi ...

Utilizing a drop-down menu to display two distinct sets of data tables

After spending some time on my WordPress site, I've encountered a problem that has me feeling stuck. Currently, I have two functioning datatables which are displaying one on top of the other on the page. Additionally, there is a dropdown selection box ...