DEV Community

Cover image for Using a Promise in MemoryStore to improve performance
Tyler Steck
Tyler Steck

Posted on

Using a Promise in MemoryStore to improve performance

In this example, we define a MemoryStore class that uses a Map to store promises for previously fetched resources. The get method takes in a URL and a function that returns a promise to fetch the resource at that URL. If the URL is already in the cache, the method returns the existing promise. Otherwise, the method calls the fetchData function to get a new promise, stores it in the cache, and returns it.

We also define a ResourceController class that uses the MemoryStore class to improve the performance of resource requests. The getResource method takes in a request object and a response object and extracts the URL from the request parameters. It then creates a fetchData function that uses the fetch API to fetch the resource at the specified URL. It calls the get method of the MemoryStore object to get a promise for the resource, and then uses the then method to extract the text body of the response and send it back in the response.

The clearCache method simply calls the clear method of the MemoryStore object to clear the cache.

Finally, we define an Express.js app and set up two endpoints for the getResource and clearCache methods. We create an instance of the ResourceController class and pass it to the route handlers.

Note that in order to use this class, you'll need to have the fetch API available in your project. Also, make sure to handle any errors that may occur during the fetching process, such as network errors or errors with the resource server.

import express, { Request, Response } from 'express';

class MemoryStore {
  private cache: Map<string, Promise<Response>>;

  constructor() {
    this.cache = new Map();
  }

  get(url: string, fetchData: () => Promise<Response>): Promise<Response> {
    if (this.cache.has(url)) {
      return this.cache.get(url)!;
    }

    const promise = fetchData();

    this.cache.set(url, promise);

    return promise;
  }

  clear(): void {
    this.cache.clear();
  }
}

class ResourceController {
  private store: MemoryStore;

  constructor() {
    this.store = new MemoryStore();
  }

  getResource(req: Request, res: Response): void {
    const url = req.params.url;

    const fetchData = async () => {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error(`Failed to fetch resource from ${url}`);
      }

      return response;
    };

    const promise = this.store.get(url, fetchData);

    promise
      .then(response => response.text())
      .then(body => {
        res.send(body);
      })
      .catch(error => {
        console.error(error);
        res.status(500).send('Error fetching resource');
      });
  }

  clearCache(req: Request, res: Response): void {
    this.store.clear();
    res.send('Cache cleared successfully');
  }
}

const app = express();

const controller = new ResourceController();

app.get('/resource/:url', (req, res) => controller.getResource(req, res));
app.delete('/cache', (req, res) => controller.clearCache(req, res));

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Using Supertest to test the getResource and clearCache methods of the ResourceController

import supertest from 'supertest';
import { MemoryStore, ResourceController } from './ResourceController';

describe('ResourceController', () => {
  let store: MemoryStore;
  let controller: ResourceController;
  let app: Express.Application;

  beforeEach(() => {
    store = new MemoryStore();
    controller = new ResourceController(store);
    app = express();
    app.get('/resource/:url', (req, res) => controller.getResource(req, res));
    app.delete('/cache', (req, res) => controller.clearCache(req, res));
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  describe('getResource', () => {
    it('should return resource body when resource is fetched successfully', async () => {
      const expectedBody = 'Resource body';
      const url = 'http://example.com/resource';
      const response = new Response(expectedBody);
      jest.spyOn(global, 'fetch').mockResolvedValueOnce(response);

      const result = await supertest(app).get(`/resource/${url}`).expect(200);

      expect(global.fetch).toHaveBeenCalledWith(url);
      expect(result.text).toBe(expectedBody);
    });

    it('should return 500 error when resource fetch fails', async () => {
      const url = 'http://example.com/resource';
      jest.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Failed to fetch resource'));

      const result = await supertest(app).get(`/resource/${url}`).expect(500);

      expect(global.fetch).toHaveBeenCalledWith(url);
      expect(result.text).toBe('Error fetching resource');
    });
  });

  describe('clearCache', () => {
    it('should clear the memory store cache', async () => {
      const url = 'http://example.com/resource';
      const response = new Response('Resource body');
      jest.spyOn(global, 'fetch').mockResolvedValueOnce(response);

      await supertest(app).get(`/resource/${url}`);
      await supertest(app).delete('/cache').expect(200);

      expect(store['cache'].size).toBe(0);
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

In this example, we import the supertest library and define a Jest test suite for the ResourceController class. We create a new MemoryStore object and a new ResourceController object using the store, and we define an Express.js app with routes that use the controller methods.

We then define two test cases for the getResource method. The first test case simulates a successful fetch request, sets up a mock response from the fetch API using Jest, and uses supertest to make a request to the app. It expects a 200 response code and the expected resource body. The second test case simulates a failed fetch request and expects a 500 error response.

Finally, we define a test case for the clearCache method that makes a request to the app to clear the cache and checks that the cache property of the MemoryStore object is empty.

Note that in this example, we use the global.fetch method from the Jest environment to set up mock responses for the fetch API. If you're using a different testing environment, you may need to use a different method to mock the fetch API.

Top comments (0)