Exploring the creation of a streamlined resource locking system in TypeScript - quick demo with a test scenario attached

I am faced with a challenge on my server where I receive concurrent requests, but certain R/W resources cannot be accessed simultaneously. Implementing locks at the database level is not practical for me, so I am looking to create a function that can turn parallel requests into sequential ones.

My idea involves having queues with individual resource IDs. When a request comes in, if the corresponding queue for the ID is empty, the desired function should be executed. If not, the function should be queued to run after the others have finished.

Despite coming up with some code to achieve this, I am encountering issues and need help figuring out why it's not working as expected.

You can view the code on this example fiddle.

export const sleep = (ms: number): Promise<void> =>
  new Promise((resolve) => { setTimeout(resolve, ms); });

type AsyncFn = () => Promise<void>;

const locks = new Map<string, AsyncFn[]>();

const unstackLock = async (id: string) => {
  const stack = locks.get(id);

  if (!stack) {
    return;
  }

  const nextFn = stack.shift();

  if (!nextFn) {
    return;
  }

  try {
    await nextFn();
  } finally {
    await unstackLock(id);
  }
};

export const withLock = async (id: string, fn: AsyncFn): Promise<void> => {
  if (!locks.has(id)) {
    locks.set(id, []);
  }

  const lock = locks.get(id);

  if (!lock) {
    // never happens but makes TS happy
    throw new Error('Lock is not defined');
  }

  lock.push(fn);

  if (lock.length === 1) {
    await unstackLock(id);
  }
};

const test = async () => {
  const results: number[] = [];
    
  const f1 = withLock('lock', async () => {
    await sleep(Math.random() * 100);
    results.push(1);
  });

  const f2 = withLock('lock', async () => {
    await sleep(Math.random() * 100);
    results.push(2);
  });

  const f3 = withLock('lock', async () => {
    await sleep(Math.random() * 100);
    results.push(3);
  });

  await Promise.all([f1, f2, f3]);

  if (results[0] !== 1 || results[1] !== 2 || results[2] !== 3) {
    console.log('FAILED', results);
  } else {
    console.log('SUCCESS');
  }
};

test();

I aim to have the results array filled with [1, 2, 3] in the same order as long as f1, f2, and f3 execute. However, the current execution order appears to be random.

Answer №1

Here's a different approach to achieve the desired result:

const sleepFunction = async (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
};

const asyncFunction = () => {
    return new Promise(resolve => {
        // Some asynchronous task
        resolve();
    });
};

let isTaskRunning = false;
let queue = [];

const executeTask = async () => {
    if (!isTaskRunning && queue.length > 0) {
        let fn = queue.shift();
        isTaskRunning = true;
        await fn();
        isTaskRunning = false;
        await executeTask();
    }
};

const addAsyncTaskToQueue = (fn) => {
    queue.push(fn);
    executeTask();
};

const sampleTest = async () => {
    const results = [];
    
    addAsyncTaskToQueue(async () => {
        await sleepFunction(Math.random() * 100);
        results.push(1);
    });

    addAsyncTaskToQueue(async () => {
        await sleepFunction(Math.random() * 100);
        results.push(2);
    });

    addAsyncTaskToQueue(async () => {
        await sleepFunction(Math.random() * 100);
        results.push(3);
    });

    await Promise.all(queue);

    if (results[0] !== 1 || results[1] !== 2 || results[2] !== 3) {
        console.log('Failed', results);
    } else {
        console.log('Success');
    }
};

sampleTest();

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

Ways to overlook compilation errors in Typescript/Electron for unreached code

I am diving into Typescript/Electron and attempting to create a text-based game. My journey began with a basic Electron application and I started implementing core logic using classes/interfaces that reference classes I have yet to implement. The snippet o ...

Tips for correctly specifying the theme as a prop in the styled() function of Material UI using TypeScript

Currently, I am utilizing Material UI along with its styled function to customize components like so: const MyThemeComponent = styled("div")(({ theme }) => ` color: ${theme.palette.primary.contrastText}; background-color: ${theme.palette.primary.mai ...

Struggling to successfully compile and release my Typescript library with webpack or TSC

I've developed a library that uses rx streams to track the state of an object, and now I'm looking to share it with the npm community. You can find the code on my Github repository. My goal is to compile the library into a single Javascript fil ...

Identify the category of the component

Using a Link component from version 4.0.0-beta.2, I am exploring its capability to override the root element with a field called component. My goal is to wrap the Link component in a new component called MyLink and pass a custom component through props: ...

Issue: Unable to call method "call" as the model "Model" has not been initialized within a Sequelize instance. Kindly ensure that "Model" is added to a Sequelize instance before attempting to use the "call" method

Author.ts import {Table, Model, Column, DataType} from 'sequelize-typescript' @Table export class Author extends Model<Author> { constructor(){ super(); } @Column(DataType.STRING) fname: string @Column(DataType.STRING) lname: strin ...

The 'cookies' property is not found on the 'Request' type

Currently, I am attempting to access a cookie within a NestJS controller. I have been referencing the documentation found at https://docs.nestjs.com/techniques/cookies#use-with-express-default Below is my implementation: import { Controller, Get, Render, ...

Utilize Angular's $state service within a configuration setting to automatically redirect to a specific state using an interceptor

I'm working with restangular and have set up an interceptor to handle 401 responses by redirecting to another state. The issue is that angular only allows the injection of providers, not services, in config. Is there a way to access the $state: ng.u ...

Ensuring Map Safety in Typescript

Imagine having a Map structure like the one found in file CategoryMap.ts export default new Map<number, SubCategory[]>([ [11, [100, 101]], [12, [102, 103]], ... ]) Is there a way to create a type guard for this Map? import categoryMap fro ...

Error with React, key must be unique. What's the issue?

What is causing the issue with unique keys? To resolve the problem, ensure that each list item has a unique key. For example, if we have x_values = {'male':[1,2,3], 'female':[2,3,4]} the keys should be : 'mean-male', ' ...

The dynamic duo of Typescript and Express creates an unbreakable bond within a configuration

Trying to incorporate ES6 modules into my app has been a bit frustrating. Initially, I attempted setting "module": "es2020" or "module": "esnext", only to encounter an error instructing me to specify "type": "module" in the package.json file or use the .m ...

Why is my Vue view not being found by Typescript (or possibly Webpack)?

npx webpack TS2307: Cannot locate module './App.vue' or its corresponding type declarations. I am currently utilizing webpack, vue, and typescript. My webpack configuration is pretty basic. It uses a typescript file as the entry point and gener ...

Tips on how to modify the session type in session callback within Next-auth while utilizing Typescript

With my typescript setup, my file named [...next-auth].tsx is structured as follows: import NextAuth, { Awaitable, Session, User } from "next-auth"; // import GithubProvider from "next-auth/providers/github"; import GoogleProvider from ...

Tips for acquiring the newest router in an angular environment

Is there a way to retrieve and store the URL of the latest router that the user has visited in local storage? Any suggestions would be greatly appreciated. Thank you! ...

Function that returns a lookup map for TypeScript enums

For my React project, I've created a function that transforms a lookup class into an array that can be used. The function is functioning properly, but it seems to loop through the enum twice, resulting in undefined values for the first iteration. Alt ...

Automatically deducing types from object keys in Typescript is a convenient feature

I'm looking to define an interface for a Select component that allows for selecting single or multiple items. interface MySelect<T extends boolean> { multi: T, // Indicates if it's a multiple item select onChange: (item: T extends t ...

Error: The property you are trying to destructure is undefined, please define it before destructuring

I'm struggling to render the 'password confirmation input' and the 'button', as it's not working at all. I'm unsure of what changes need to be made. TypeError: Cannot destructure property '' of '' sinc ...

Error with React Query Mutation and TypeScript: The argument '{ surgeryID: any; stageTitle: any; }' cannot be assigned to a parameter of type 'void'

Utilizing react-query for fetching and posting data to my database on supabase has been really helpful. I took the initiative to create a custom hook specifically for adding records using react-query: export function useAddSurgeryStage() { const { mutate ...

Using variables to replace 'placeholders' in Typescript with string interpolation

Seeking a definitive answer on this matter, I pose the question: In C#, an example of which would be as follows: var text = "blah blah"; var strTest = String.Format("This is a {0}", text); //output: 'This is a blah blah' How can I accomplish t ...

invoking a function at a designated interval

I am currently working on a mobile application built with Ionic and TypeScript. My goal is to continuously update the user's location every 10 minutes. My approach involves calling a function at regular intervals, like this: function updateUserLocat ...

Webpack does not support d3-tip in its current configuration

I'm having some trouble getting d3-tip to work with webpack while using TypeScript. Whenever I try to trigger mouseover events, I get an error saying "Uncaught TypeError: Cannot read property 'target' of null". This issue arises because th ...