Using a Promise in MemoryStore to improve performance
Published: 2023-02-28 04:35:45

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');
});
```

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

```typescript
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);
    });
  });
});
```

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.