Async Cache Service - you don't need "isLoading" anymore!
Tags: githubhack23, nx, typescript, javascript
Published: 2023-05-23 02:07:27

## 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](https://www.npmjs.com/package/async-cache-service)

### Screenshots 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fpla6xfdu29q7s3k2r9y.png)

### 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:

```ts
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)) {}
}
```
Javascript Example:

```javascript
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);
  }
}
```
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](https://github.com/frozer/async-cache-service)

### Permissive License 
This project is under [MIT License](https://github.com/frozer/async-cache-service/blob/main/LICENSE)

## Background (What made you decide to build this particular app? What inspired you?)
In the preceding article [Vanilla JS data cache service](https://dev.to/frozer/vanilla-js-data-cache-service-1ei2), 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 system](https://nx.dev)it 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 :
```yml
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 }}
```
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:
 
```yml
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
```

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:
```yml
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 }}
```
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
* [Github Action different between release created and published](https://stackoverflow.com/questions/59319281/github-action-different-between-release-created-and-published)
* [Storing workflow data as artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts)
* [Publishing Node.js packages](https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages)