DEV Community

Denis Morozov
Denis Morozov

Posted on

Async Cache Service - you don't need "isLoading" anymore!

What I built

Simple Javascript library to manage asynchronous cache/storage in app/service.

Category Submission:

The best category matches my project is Wacky Wildcards

App Link

You can find library page and package here - Async Cache Service on NPM

Screenshots

Image description

Description

The asynchronous cache service is designed to handle the storage and retrieval of data in a non-blocking manner. When data is being loaded into the cache, the service returns a Promise object to the requester. This Promise serves as a placeholder or notification that the data is currently in the process of being fetched.

Once the data loading process is complete and the data is successfully retrieved, the cache service resolves all previously issued Promises associated with that specific data. By resolving the Promises, the cache service effectively provides the updated and complete data to all subscribers or requesters who were waiting for it.

How to use

Typescript Example:

import { AsyncCacheService } from 'async-cache-service';

// by default - the cache is never expire
const cacheService = new AsyncCacheService<string>();

// with 5min expiration
const cacheService = new AsyncCacheService<string>(300_000);

// as a dependency with 15min expiration
export class SomeDataService {
  constructor(private cacheService = new AsyncCacheService<string>(900_000)) {}
}
Enter fullscreen mode Exit fullscreen mode

Javascript Example:

import {AsyncCacheService} from 'async-cache-service';

class DataService {
  // cache expires in 15min
  constructor(cacheService = new AsyncCacheService(15 * 60 * 1000)) {
    this.cacheService = cacheService;
  }

  async getData(id) {
    if (this.cacheService.isExpired(id) {
      this.cacheService.refreshItem(id);

      try {
        // retrieve data from somewhere
        const data = await axios.get('/some/resource');
        this.cacheService.setItem(id, data);
      } catch (e) {
        this.cacheService.flushItem(id);
      }
    }

    return this.cacheService.getItem(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

When the getData method of DataService is called, it returns a Promise, and DataService initiates the data load. During this process, all incoming calls to getData will receive Promises that resolve after the data load. In other words, we eliminate the necessity to have a well-known isLoading flag in our DataService and provide an elegant way to handle multiple data requests.

Link to Source Code

You can find library sources here - Async Cache Service on Github

Permissive License

This project is under MIT License

Background (What made you decide to build this particular app? What inspired you?)

In the preceding article Vanilla JS data cache service, I emphasized the issue of asynchronous data cache and outlined a potential solution. However, after thoughtful consideration, I opted to integrate the suggested approach into a compact Javascript library, accompanied by comprehensive unit tests and leveraging Github Actions for complete CI/CD workflow - encompassing build -> test -> package -> deploy. The resultant library, named "async-data-service", introduces an efficient method to manage asynchronous cache/storage operations with minimal developer overhead.

How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)

Well, I used TypeScript (love it!) and Rollup for bundling. For my projects, I prefer to use the Nx build systemit provides me with a lot of pre-defined tools and allows me to focus on what I do. For unit tests, I used the Jest framework. The real challenge for me in this project was to utilize GitHub Actions for NPM publishing.

The most available resources describe the publishing process in a pretty straightforward way, like (taken from official :

name: Publish Package to npmjs
on:
  release:
    types: [published]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      # Setup .npmrc file to publish to npm
      - uses: actions/setup-node@v3
        with:
          node-version: '16.x'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Which isn't really helpful. How does "npm ci" relate to "npm publish"? What is the difference between a "published" release and a "created" release? And finally, where are my built artifacts?

So, I need to find answers. The first thing I resolved was the question regarding the difference between "created" and "published" events for releases. Actually, the "created" event is emitted only for draft releases, while the "published" event is emitted for non-draft releases. Therefore, I will use the "published" event in my Github Actions.

Next, I have to consider that Nx puts build artifacts into the "dist/packages/async-cache-service" folder. Therefore, I need to run "npm publish" from there. As a result of my GitHub Actions efforts, I implemented two actions - "push" and "publish".

The "push" action is used to handle the push event of a pull request and performs the build and test of the proposed changes:

name: Node.js CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm run build --if-present
      - run: npm test
Enter fullscreen mode Exit fullscreen mode

The "publish" action is used to handle the release publish event and performs the build, test, and publish steps for the release in two steps:

name: Publish Package to npmjs
on:
  release:
    types: [published]
jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: |
          npm ci
          npm run build --if-present
          npm test 
      - name: Archive production artifacts
        uses: actions/upload-artifact@v3
        with:
          name: artifact
          path: dist/packages/async-cache-service

  download:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build step artifact
        uses: actions/download-artifact@v3
        with:
          name: artifact
      - uses: actions/setup-node@v3
        with:
          node-version: '18.x'
          registry-url: 'https://registry.npmjs.org'
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

As you can see, this action contains two steps - build and publish. The build step performs the build, test, and stores the build artifacts into the Github storage. The publish step retrieves the just created build from the build node and publishes it into the NPM registry.

In summary, I find myself very interested in using GitHub Actions for my current and future projects. I love the way it works. My next step is to adopt GitHub Actions for another project I'm working on, written in C.

Additional Resources/Info

Top comments (0)