Issue with Typescript Class Variable Not Maintaining its Value

In my quest to develop a class that can retrieve/cache users from my Firestore database, I've encountered an issue where I'm unable to save or expose the previous promise that was generated. Below is the code snippet of my class:

export class UserCache {
  private cacheTimeMilliseconds: number = 600000;
  private userCache: any = {};

  public getCacheUser(userid: string): Promise<User> {
    return new Promise((resolve, reject) => {
      let d = new Date();
      d.setTime(d.getTime() - this.cacheTimeMilliseconds);
      if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
        console.log("User cached");
        resolve(this.userCache[userid].user);
      }

      console.log("Need to cache user");
      this.userCache[userid] = {
        complete: false
      };
      this.getSetUserFetchPromise(userid).then((data) => {
        let user: User = <User>{ id: data.id, ...data.data() };
        this.userCache[userid].user = user;
        this.userCache[userid].complete = true;
        this.userCache[userid].lastAccess = Date.now();
        resolve(user);
      });
    });
  }

  private getSetUserFetchPromise(userid: string): Promise<any> {
    console.log(this.userCache[userid]);
    if (this.userCache[userid] && this.userCache[userid].promise) {
      return this.userCache[userid].promise;
    } else {
      console.log("Creating new user fetch request.");
      this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
      console.log(this.userCache[userid]);
      return this.userCache[userid].promise;
    }
  }
}

The logs reveal that the promise is being set in getSetUserFetchPromise, but upon subsequent function calls, the property seems to be unset. This leads me to suspect it's either a scope or concurrency issue, which I'm struggling to address.

I invoke getCacheUser in a consuming class using let oCache = new UserCache() and oCache.getCacheUser('USERID')

Following Tuan's answer below

UserCacheProvider.ts

import firestore from '@react-native-firebase/firestore';
import { User } from '../static/models';

class UserCache {
  private cacheTimeMilliseconds: number = 600000;
  private userCache: any = {};

  public getCacheUser(userid: string): Promise<User> {
    return new Promise((resolve, reject) => {
      let d = new Date();
      d.setTime(d.getTime() - this.cacheTimeMilliseconds);
      if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
        console.log("User cached");
        resolve(this.userCache[userid].user);
      }

      console.log("Need to cache user");
      this.userCache[userid] = {
        complete: false
      };
      this.getSetUserFetchPromise(userid).then((data) => {
        let user: User = <User>{ id: data.id, ...data.data() };
        this.userCache[userid].user = user;
        this.userCache[userid].complete = true;
        this.userCache[userid].lastAccess = Date.now();
        resolve(user);
      });
    });
  }

  private getSetUserFetchPromise(userid: string): Promise<any> {
    console.log(this.userCache[userid]);
    if (this.userCache[userid] && this.userCache[userid].promise) {
      return this.userCache[userid].promise;
    } else {
      console.log("Creating new user fetch request.");
      this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
      console.log(this.userCache[userid]);
      return this.userCache[userid].promise;
    }
  }
}

const userCache = new UserCache();
export default userCache;

ChatProvider.ts (usage)

let promises = [];

          docs.forEach(doc => {
            let message: Message = <Message>{ id: doc.id, ...doc.data() };

            promises.push(UserCacheProvider.getCacheUser(message.senderid).then((oUser) => {
              let conv: GCMessage = {
                _id: message.id,
                text: message.messagecontent,
                createdAt: new Date(message.messagedate),
                user: <GCUser>{ _id: oUser.id, avatar: oUser.thumbnail, name: oUser.displayname }
              }

              if (message.type && message.type == 'info') {
                conv.system = true;
              }

              if (message.messageattachment && message.messageattachment != '') {
                conv.image = message.messageattachment;
              }

              return conv;
            }));
          });

          Promise.all(promises).then((values) => {
            resolve(values);
          });

Answer №1

It seems that the issue could stem from getCacheUser being invoked multiple times before Firestore resolves.

On another note, restructuring the class might enhance the debugging process. One may question the reason behind caching user data, promise completion status, and the promise itself separately. Perhaps it would be more efficient to solely cache the promise, as illustrated below:

interface UserCacheRecord {
    promise: Promise<User>
    lastAccess: number
}

export class UserCache {
    private cacheTimeMilliseconds: number = 600000;
    private userCache: { [userid: string]: UserCacheRecord } = {};

    public async getCacheUser(userid: string): Promise<User> {
        let d = new Date();
        const cacheExpireTime = d.getTime() - this.cacheTimeMilliseconds

        if (this.userCache[userid] && this.userCache[userid].lastAccess > cacheExpireTime) {
            console.log("User cached");
            return this.userCache[userid].promise
        }

        console.log("Need to cache user");

        this.userCache[userid] = {
            promise: this.getUser(userid),
            lastAccess: Date.now()
        }

        return this.userCache[userid].promise
    }

    private async getUser(userid: string): Promise<User> {
        const data = firestore().collection('users').doc(userid).get();
        return <User>{ id: data.id, ...data.data() };
    }
}

Answer №2

At this moment, a new instance of UserCache is created every time the cache users are accessed. It would be more efficient to export the instance of the UserCache class so that only a single instance is utilized throughout your application.

UserCache.ts

class UserCache {
}

const userCacheInstance = new UserCache();
export default userCacheInstance;

SomeFile.ts

import UserCache from './UserCache';

UserCache.getCacheUser('USERID')

Update

Added some tests

class UserCache {
  userCache = {};

  getUser(id) {
    return new Promise((resolve, reject) => {
      if (this.userCache[id]) {
        resolve({
          ...this.userCache[id],
          isCache: true,
        });
      }
      this.requestUser(id).then(data => {
        resolve(data);
        this.userCache[id] = data;
      });
    });
  }

  requestUser(id) {
    return Promise.resolve({
      id,
    });
  }
}

const userCacheInstance = new UserCache();
export default userCacheInstance;

userCache.test.ts

import UserCache from '../test';

describe('Test user cache', () => {
  test('User cached successfully', async () => {
    const user1: any = await UserCache.getUser('test1');
    expect(user1.isCache).toBeUndefined();
    const user2: any = await UserCache.getUser('test1');
    expect(user2.isCache).toBe(true);
  });
});

https://i.sstatic.net/GSByg.png

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

Update a DIV when ajax call is successful

I have a webpage with a specific heading: <div class="something"><? some php code ?></div> On this page, there is also an ajax function that performs a certain task: <script> $(document).ready(function () { $(document).ajaxSta ...

Similar to TypeScript's `hasOwnProperty` counterpart

When working with TypeScript objects, is there a way to loop through a dictionary and set properties of another dictionary similar to how it is done in JavaScript? for (let key in dict) { if (obj.hasOwnProperty(key)) { obj[key] = dict[key]; } } If ...

Guide to dynamically swapping one SVG icon with another SVG icon using AngularJS

In my current setup, I am working with a dedicated framework that requires me to specify the path to an svg icon like so: <sit-command-bar sit-type="action" sit-layout="vertical"> <sit-command svg-icon="common/ ...

Angular Protractor: Leveraging Browser Context for Executing Scripts

Within my index.html file, I explicitly define the following: window.myAppInstance = new MyApp.myAppConstructor(); In the todo-spec.js file, I set up the following structure: describe('verifying my web page', function() { it('should con ...

What is the best approach for managing errors within a shared observable in TypeScript?

I'm facing a unique issue and struggling to find someone who has encountered the same problem, which could imply that I am approaching it incorrectly. The http request I am making looks like this: return this.httpClient.post(`${this.route}/typegroups ...

Do not start Chart.js on the Y-Axis

As of now, this is the current appearance of the code which includes a Chart.js first image preview. I am seeking advice on how to prevent my data chart from starting at the Y axis. The result of the code can be viewed below. https://i.sstatic.net/sdl3K. ...

Getting information from MySQL database with just HTML and JavaScript

Looking to securely access a MySQL database on my localhost using only HTML and JavaScript. No need for server-side scripting or web servers. Any tips on how to make this happen? ...

Tips for stopping screen recording on a webpage while using a browser

I am currently working on developing an educational website for a client. He has requested that I include a feature to prevent users from recording the screen on the website, as it will contain premium content and Vimeo videos that he wants to protect fr ...

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 ...

Tips on connecting data within a jQuery element to a table of data

I am currently developing a program that involves searching the source code to list out element names and their corresponding IDs. Instead of displaying this information in alert popups, I would like to present it neatly within a data table. <script> ...

What could be preventing the registered user from being authenticated in Firebase? Why are they unable to successfully upload products to the Firebase database?

How can I enable the owner, who is authenticated, to add or modify products in my project? What steps should I take to allow registered users to add products to the catalog from the Administrator Panel? I have developed a web application using npx create ...

In my JavaScript code, I am parsing a JSON file and attempting to extract the data associated with a specific key

Here's the javascript code I'm working on. I am looping through a JSON file and trying to access the 'bought_together' values nested under 'related', but my current code doesn't seem to be doing what I want it to do. To d ...

How to transform a file into a uInt8Array using Angular

Looking to implement a feature where I can easily upload files from Angular to PostgreSQL using a Golang API. In my Angular component, I need to convert my file into a uInt8Array. I have managed to convert the array, but it seems to be encapsulated in som ...

Looking for a jQuery plugin that creates a sticky top menu bar?

I am in search of a jQuery plugin (or code/guide) that can achieve this effect: Unfortunately, this particular one is not available for free Please take note that the navigation bar does not initially appear at the top but sticks once the viewport reache ...

Disabling the Left swipe gesture in React Native: A Step-by-Step Guide

I've been working on integrating createStackNavigator and createDrawerNavigator into my app. Everything seems to be functioning properly, but I've encountered a roadblock that I can't seem to overcome. Specifically, I'm using the Drawer ...

How can API calls be efficiently made in React?

While working in React, I encountered a minor issue when calling the API because the ComponentWillMount method is deprecated. Here is what I did: class MyClass extends Component { constructor() { super(); this.state = { questionsAnswers: [ ...

A guide to combining two properties from a single object within an object using JavaScript

let aboutMark = { firstName: "Mark", lastName: "Miller", height: 1.69, weight: 78, bmiCalculator: function(){ this.markBMI = (this.weight / (this.height * this.height)) return this.markBMI } }; aboutMark.bmiCalculator() console.log(ab ...

Ensuring the Safety of CloudKit.js

After reviewing the example provided by Apple in the CloudKit catalog, I noticed that every time you want to write or read data, you must include your API token in the script. Since Javascript is client-based, does this mean that any user can access and m ...

The nested transclude directive is displaying the inner transcluded content in the incorrect location

I created a directive called myList that transcludes its content. The issue arises when I try to nest a <my-list> element inside another <my-list>. Check out the JS Fiddle here: http://jsfiddle.net/fqj5svhn/ The directive is implemented as fo ...

Exploring smooth scrolling functionality using AngularJS and integrating it with IFrames

After implementing an angular controller, I included the following code: angular.element(document).ready(function () { ... } Within this setup, I added a function to enable smooth scrolling to the hash of window.location.hash using .animate({scrollTop... ...