{"id":18233263,"url":"https://github.com/grimmerk/d4c-queue","last_synced_at":"2025-04-03T19:30:59.530Z","repository":{"id":46087788,"uuid":"360446202","full_name":"grimmerk/d4c-queue","owner":"grimmerk","description":"Execute tasks sequentially or concurrently. Wrap an async/promise-returning/sync function as a queue-ready async function for easy reusing. Support passing arguments/getting return value, decorators. below is an introduction to v1.6.","archived":false,"fork":false,"pushed_at":"2021-11-19T16:44:29.000Z","size":648,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T10:59:54.551Z","etag":null,"topics":["apollo","await","browser","concurency","concurrency","decorators","javascript","lock","nestjs","nodejs","promise-returning-functions","sequential","synchronization","task-queue","throttle","typescript"],"latest_commit_sha":null,"homepage":"https://slides.com/grimmer/intro_js_ts_task_queuelib_d4c","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/grimmerk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-22T08:26:13.000Z","updated_at":"2024-02-03T17:30:06.000Z","dependencies_parsed_at":"2022-08-30T21:22:14.716Z","dependency_job_id":null,"html_url":"https://github.com/grimmerk/d4c-queue","commit_stats":null,"previous_names":["grimmerk/d4c-queue","grimmer0125/d4c-queue"],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grimmerk%2Fd4c-queue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grimmerk%2Fd4c-queue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grimmerk%2Fd4c-queue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grimmerk%2Fd4c-queue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/grimmerk","download_url":"https://codeload.github.com/grimmerk/d4c-queue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246758354,"owners_count":20828919,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apollo","await","browser","concurency","concurrency","decorators","javascript","lock","nestjs","nodejs","promise-returning-functions","sequential","synchronization","task-queue","throttle","typescript"],"created_at":"2024-11-04T15:03:31.692Z","updated_at":"2025-04-03T19:30:59.260Z","avatar_url":"https://github.com/grimmerk.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# D4C Queue\n\n[![npm version](https://img.shields.io/npm/v/d4c-queue.svg)](https://www.npmjs.com/package/d4c-queue) ![example workflow](https://github.com/grimmer0125/d4c-queue/actions/workflows/node.js.yml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/grimmer0125/d4c-queue/badge.svg)](https://coveralls.io/github/grimmer0125/d4c-queue)\n\nWrap an [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)/[promise-returning](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)/`sync` function as a queue-ready async function, which is enqueued while being called. This is convenient to reuse it. In synchronization mode, task queues execute original functions sequentially by default (equivalently `concurrency limit = 1`). In concurrency mode, it allows changing concurrency limit to have concurrent tasks executed. It also supports `@synchronized`/`@concurrent` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on instance or static methods. Passing arguments and using `await` to get return values are also supported.\n\n## Features\n\n1. Two usages\n   1. D4C instance: synchronization mode \u0026 concurrency mode.\n   2. Class, instance, and static method decorators on classes: synchronization mode \u0026 concurrency mode.\n2. Wrap a function to a new queue-ready async function. It is convenient to re-use this function. Also, it is able to pass arguments and get return value for each task function.\n3. Support `async function`, a `promise-returning` function, and a `sync` function.\n4. Sub queues system (via tags).\n5. Support Browser and Node.js.\n6. Fully Written in TypeScript and its `.d.ts` typing is out of box. JavaScript is supported, too.\n7. This library implements a FIFO task queue for O(1) speed. Using built-in JavaScript array will have O(n) issue.\n8. Well tested.\n9. Optional parameter, `inheritPreErr`. If current task is waiting for previous tasks, set it as `true` to inherit the error of the previous task and the task will not be executed and throw a custom error `new PreviousError(task.preError.message ?? task.preError)`. If this parameter is omitted or set as `false`, the task will continue whether previous tasks happen errors or not.\n10. Optional parameter, `noBlockCurr`. Set it as `true` to forcibly execute the current task in the another (microtask) execution of the event loop. This is useful if you pass a sync function as the first task but do not want it to block the current event loop.\n11. Optional parameter, `dropWhenReachLimit`. Set it as `true`, then this function call will be dropped when the system detects the queue concurrency limit is reached. It is like a kind of throttle mechanism but not time interval based. The dropped function call will not be really executed and will throw a execption whose message is `QueueIsFull` and you need to catch it.\n\n## Installation\n\nThis package includes two builds.\n\n- ES6 build (ES2015) with CommonJS module for `main` build in package.json.\n- ES6 build (ES2015) with ES6 module for `module` build. Some tools will follow the `module` field in `package.json`, like Rollup, Webpack, or Parcel.\n\nEither `npm install d4c-queue` or `yarn add d4c-queue`. Then import this package.\n\n**ES6 import**\n\n```typescript\nimport { D4C, synchronized, QConcurrency, concurrent } from 'd4c-queue'\n```\n\n**CommonJS**\n\n```typescript\nconst { D4C, synchronized, QConcurrency, concurrent } = require('d4c-queue')\n```\n\nIt is possible to use the `module` build with CommonJS require syntax in TypeScript or other build tools.\n\n### Extra optional steps if you want to use decorators from this library\n\nKeep in mind that `decorators` are JavaScript proposals and may vary in the future.\n\n#### TypeScript users\n\nModify your tsconfig.json to include the following settings\n\n```json\n{\n  \"experimentalDecorators\": true\n}\n```\n\n#### JavaScript users\n\nYou can use Babel to support decorators, install `@babel/plugin-proposal-decorators`.\n\nFor the users using **Create React App** JavaScript version, you can either use `eject` or [CRACO](https://github.com/gsoft-inc/craco) to customize your babel setting. Using create React App TypeScript Version just needs to modify `tsconfig.json.`\n\nSee [babel.config.json](#babelconfigjson) in [Appendix](#Appendix).\n\nSee [CRACO Setting](#craco-setting) in [Appendix](#Appendix).\n\n## Usage example\n\nKeep in mind that a function will not be enqueued into a task queue even it becomes a new function after wrapping. A task will be enqueued only when it is executed.\n\n### Designed queue system\n\nEach queue is isolated with the others.\n\n- Two instance of your decorated class will have two individual queue system.\n  - The default queue in instance method queues is something like `@synchronized(self)` in other languages.\n- Each D4C instance will have its own queue system.\n\n```\nD4C queues (decorator) injected into your class:\n  - instance method queues (per instance):\n      - default queue\n      - tag1 queue\n      - tag2 queue\n  - static method queues\n      - default queue\n      - tag1 queue\n      - tag2 queue\nD4C instance queues (per D4C object):\n  - default queue\n  - tag1 queue\n  - tag2 queue\n```\n\n### D4C instance usage\n\n#### Synchronization mode\n\n```typescript\nconst d4c = new D4C()\n\n/**\n * in some execution of event loop\n * you can choose to await the result or not.\n */\nconst asyncFunResult = await d4c.wrap(asyncFun)(\n  'asyncFun_arg1',\n  'asyncFun_arg2'\n)\n/**\n * in another execution of event loop. Either async or\n * sync function is ok. E.g., pass a sync function,\n * it will wait for asyncFun's finishing, then use await to get\n * the new wrapped async function's result.\n */\nconst syncFunFunResult = await d4c.wrap(syncFun)('syncFun_arg1')\n```\n\nAlternatively, you can use below\n\n```typescript\nd4c.apply(syncFun, { args: ['syncFun_arg1'] })\n```\n\n#### Concurrency mode\n\nIs it useful for rate-limiting or throttling tasks. For example, setup some concurrency limit to avoid send GitHub GraphQL API requests too fast, since it has rate limits control.\n\nDefault concurrency limit of D4C instance is `1` in this library.\n\nUsage:\n\n```ts\n/** change concurrency limit applied on default queues */\nconst d4c = new D4C([{ concurrency: { limit: 100 } }])\n\n/** setup concurrency for specific queue: \"2\" */\nconst d4c = new D4C([{ concurrency: { limit: 100, tag: '2' } }])\n```\n\nYou can adjust concurrency via `setConcurrency`.\n\n```ts\nconst d4c = new D4C()\n/** change concurrency limit on default queue*/\nd4c.setConcurrency([{ limit: 10 }])\n\n/** change concurrency limit for queue2 */\nd4c.setConcurrency([{ limit: 10, tag: 'queue2' }])\n```\n\nWhen this async task function is called and the system detects the concurrency limit is reached, this tasks will not be really executed and will be enqueued. If you want to drop this task function call, you can set `dropWhenReachLimit` option when wrapping/applying the task function. e.g.\n\n```ts\nconst fn1 = d4c.wrap(taskFun, { dropWhenReachLimit: true })\n\ntry {\n  await fn1()\n} catch (err) {\n  // when the concurrency limit is reached at this moment.\n  // err.message is QueueIsFull\n  console.log({ err })\n}\n```\n\n### Decorators usage\n\n#### Synchronization mode\n\n```typescript\nclass ServiceAdapter {\n  @synchronized\n  async connect() {}\n\n  @synchronized\n  async client_send_message_wait_connect(msg: string) {\n    // ...\n  }\n\n  //** parameters are optional */\n  @synchronized({ tag: 'world', inheritPreErr: true, noBlockCurr: true })\n  static async staticMethod(text: string) {\n    return text\n  }\n}\n```\n\n#### Concurrency mode\n\n`isStatic` is to specify this queue setting is for static method and default is false. omitting tag refers default queue.\n\n```ts\n/** if omitting @QConcurrency, @concurrent will use its\n * default concurrency Infinity*/\n@QConcurrency([\n  { limit: 100, isStatic: true },\n  { limit: 50, tag: '2' },\n])\nclass TestController {\n  @concurrent\n  static async fetchData(url: string) {}\n\n  @concurrent({ tag: '2', dropWhenReachLimit: true })\n  async fetchData2(url: string) {}\n\n  /** You can still use @synchronized, as long as\n   * they are different queues*/\n  @synchronized({tag:'3')\n  async connect() {}\n}\n```\n\n#### Arrow function property\n\nUsing decorators on `arrow function property` does not work since some limitation. If you need the effect of arrow function, you can bind by yourself (e.g. `this.handleChange = this.handleChange.bind(this);`) or consider [autobind-decorator](https://www.npmjs.com/package/autobind-decorator)\n\n```typescript\n@autobind\n@synchronized // should be the second line\nclient_send_message_wait_connect(msg: string) {\n  // ...\n}\n```\n\nUsing D4C instance on `arrow function property` works well.\n\n```ts\nclass TestController {\n  // alternative way\n  // @autobind\n  // bindMethodByArrowPropertyOrAutobind(){\n  // }\n\n  bindMethodByArrowPropertyOrAutobind = async () =\u003e {\n    /** access some property in this. accessible after wrapping*/\n  }\n}\nconst d4c = new D4C()\nconst res = await d4c.apply(testController.bindMethodByArrowPropertyOrAutobind)\n```\n\n## Motivation and more detailed user scenarios\n\n### Causality\n\nSometimes a task function is better to be executed after the previous task function is finished. For example, assume you are writing a adapter to use a network client library to connect to a service, either in a React frontend or a Node.js backend program, and you do not want to block current event loop (e.g. using a UI indicator to wait) for this case, so `connect` is called first, later `send_message` is triggered in another UI event. In the adapter code, usually a flag can be used and do something like\n\n```typescript\nsend_message(msg: string) {\n  if (this.connectingStatus === 'Connected') {\n    // send message\n  } else if (this.connectingStatus === 'Connecting') {\n    // Um...how to wait for connecting successfully?\n  } else (this.connectingStatus === 'Disconnected') {\n    // try to re-connect\n  }\n}\n```\n\n`Connecting` status is more ambiguous then `Disconnected` status. Now you can use a task queue to solve them. E.g.,\n\n```typescript\nclass ServiceAdapter {\n  async send_message(msg: string) {\n    if (this.connectingStatus === 'Connected') {\n      /** send message */\n      await client_send_message_without_wait_connect(msg)\n    } else if (this.connectingStatus === 'Connecting') {\n      /** send message */\n      await client_send_message_wait_connect(msg)\n    } else {\n      //..\n    }\n  }\n\n  @synchronized\n  async connect() {\n    // ...\n  }\n\n  @synchronized\n  async client_send_message_wait_connect(msg: string) {\n    // ...\n  }\n\n  async client_send_message_without_wait_connect(msg: string) {\n    // ...\n  }\n}\n```\n\n#### Another case: use D4C instance to guarantee the execution order\n\nThe code snippet is from [embedded-pydicom-react-viewer](https://github.com/grimmer0125/embedded-pydicom-react-viewer). Some function only can be executed after init function is finished.\n\n```typescript\nconst d4c = new D4C()\nexport const initPyodide = d4c.wrap(async () =\u003e {\n  /** init Pyodide*/\n})\n\n/** without d4c-queue, it will throw exception while being called\n * before 'initPyodide' is finished */\nexport const parseByPython = d4c.wrap(async (buffer: ArrayBuffer) =\u003e {\n  /** execute python code in browser */\n})\n```\n\n### Race condition\n\nConcurrency may make race condition. And we usually use a synchronization mechanism (e.g. mutex) to solve it. A task queue can achieve this.\n\nIt is similar to causality. Sometimes two function which access same data within and will result race condition if they are executed concurrently. Although JavaScript is single thread (except Node.js Worker threads, Web Workers and JS runtime), the intrinsic property of event loop may result in some unexpected race condition, e.g.\n\n```typescript\nconst func1 = async () =\u003e {\n  console.log('func1 start, execution1 in event loop')\n  await func3()\n  console.log('func1 end, should not be same event loop execution1')\n}\n\nconst func2 = async () =\u003e {\n  console.log('func2')\n}\n\nasync function testRaceCondition() {\n  func1() // if add await will result in no race condition\n  func2()\n}\ntestRaceCondition()\n```\n\n`func2` will be executed when `func1` is not finished.\n\n#### Real world cases\n\nIn backend, the practical example is to compare `Async/await` in [Express](https://expressjs.com/) framework and [Apollo](https://www.apollographql.com/docs/apollo-server/)/[NestJS](https://nestjs.com/) frameworks. [NestJS' GraphQL part](https://docs.nestjs.com/graphql/quick-start) is using Apollo and they have a different implementation than ExpressJS. [NestJS' Restful part](https://docs.nestjs.com/controllers) is the same as ExpressJS.\n\nNo race condition on two API call in `Express`, any API will be executed one by one. After async handler callback function is finished, another starts to be executed.\n\n```typescript\n/** Express case */\napp.post('/testing', async (req, res) =\u003e {\n  // Do something here\n})\n```\n\nHowever, race condition may happen on two API call in `Apollo`/`NestJS`.\n\n```typescript\n/** Apollo server case */\nconst resolvers = {\n  Mutation: {\n    orderBook: async (_, { email, book }, { dataSources }) =\u003e {},\n  },\n  Query: {\n    books: async () =\u003e books,\n  },\n}\n```\n\nTwo Apollo GraphQL queries/mutations may be executed concurrently, not like Express. This has advantage and disadvantage. If you need to worry about the possible race condition, you can consider this `d4c-queue` library, or `Database transaction` or [async-mutex](https://www.npmjs.com/package/async-mutex). You do not need to apply `d4c-queue` library on top API endpoint always, just apply on the place you worry about.\n\n#### NestJS GraphQL synchronized resolver example with this d4c-queue\n\nThe below shows how to make `hello query` become `synchronized`. Keep in mind that `@synchronized` should be below `@Query`.\n\n```typescript\nimport { Query } from '@nestjs/graphql'\nimport { synchronized } from 'd4c-queue'\n\nfunction delay() {\n  return new Promise\u003cstring\u003e(function (resolve, reject) {\n    setTimeout(function () {\n      resolve('world')\n    }, 10 * 1000)\n  })\n}\n\nexport class TestsResolver {\n  @Query((returns) =\u003e String)\n  /** without @synchronized, two resolver may print 1/2 1/2 2/2 2/2\n   *  with @synchronized, it prints: 1/2 2/2 2/2 2/2\n   */\n  @synchronized\n  async hello() {\n    console.log('hello graphql resolver part: 1/2')\n    const resp = await delay()\n    console.log('hello graphql resolver part: 2/2')\n    return resp\n  }\n}\n```\n\n### Convenience\n\nTo use async functions, sometimes we just `await async_func1()` to wait for its finishing then start to call `async_func2`. But if we also do not want to use `await` to block current event loop? The workaround way is to make another wrapper function manually to detach, like below\n\n```typescript\nasync wrap_function() {\n  await async_func1()\n  await async_func2()\n}\n\ncurrent_function() {\n  // just call\n  wrap_function()\n\n  // continue current following code\n  // ..\n}\n```\n\nUse this library can easily achieve, becomes\n\n```typescript\ncurrent_function() {\n  const d4c = new D4C();\n  d4c.apply(async_func1);\n  d4c.apply(async_func2);\n}\n```\n\n### Throttle case: avoid more and more delaying UI updating events as time grow\n\nBesides rate-limit cases (e.g. server side limit), another case is you trigger mouse move too often, and these events will cause some function calls (either calculation or API calls) and UI wait for these results to update in `await` way. It will not happen permanent UI dealy situation if these all happen in the same UI main thread since the funciton calls will avoid mouse move events been produced. But if these function calls are happend in another system (async http request) or calculation on web works (another thread), it may result in UI thread triggering too faster than the calcultion/consuming capability, which means it is over the performance bottlenect and you will see the UI updating delayed and delayed. The solution is to use `throttle` to limit the mouse event producing. This is why `dropWhenReachLimit` is introduced.\n\n## API\n\nThe parameters in the below signatures are optional. `inheritPreErr` and `noBlockCurr` are false by default. `tag` can overwrite the default tag and **specify different queue** for this method or function.\n\nYou can check the generated [TypeDoc site](https://grimmer.io/d4c-queue/modules/_lib_d4c_.html).\n\n### Decorators:\n\n- @QConcurrency\n\nsetup a array of queue settings\n\n```ts\n// use with @concurrent\nfunction QConcurrency(\n  queuesParam: Array\u003c{\n    limit: number\n    tag?: string | symbol\n    isStatic?: boolean\n  }\u003e\n) {}\n\n// example:\n@QConcurrency([\n  { limit: 100, isStatic: true },\n  { limit: 50, tag: '2' },\n])\nclass TestController {}\n```\n\n- @synchronized \u0026 @concurrent\n\n```typescript\nfunction synchronized(option?: {\n  inheritPreErr?: boolean\n  noBlockCurr?: boolean\n  tag?: string | symbol\n}) {}\n\n/** default concurrency limit is Infinity, // use with @QConcurrency */\nfunction concurrent(option?: {\n  tag?: string | symbol\n  inheritPreErr?: boolean\n  noBlockCurr?: boolean\n  dropWhenReachLimit?: boolean\n}) {}\n```\n\nExample:\n\n```typescript\n@synchronized\n@synchronized()\n@synchronized({ tag: \"world\", inheritPreErr: true })\n@synchronized({ inheritPreErr: true, noBlockCurr: true })\n\n@concurrent\n@concurrent()\n@concurrent({ tag: \"world\", inheritPreErr: true })\n@concurrent({ inheritPreErr: true, noBlockCurr: true, dropWhenReachLimit: true })\n\n```\n\nSee [decorators-usage](#decorators-usage)\n\n### D4C instance usage\n\nMake a instance first, there is a default tag so using `tag` parameter to specify some queue is optional.\n\n- constructor\n\n```ts\nconstructor(queuesParam?: Array\u003c{ tag?: string | symbol, limit?: number }\u003e) {\n```\n\nusage:\n\n```typescript\n/** default concurrency is 1*/\nconst d4c = new D4C()\n\n/** concurrency limit 500 applied on default queues */\nconst d4c = new D4C([{ concurrency: { limit: 500 } }])\n\n/** setup concurrency for specific queue: \"2\" */\nconst d4c = new D4C([{ concurrency: { limit: 100, tag: '2' } }])\n```\n\n- setConcurrency\n\n```ts\nd4c.setConcurrency([{ limit: 10 }])\n\nd4c.setConcurrency([{ limit: 10, tag: 'queue2' }])\n```\n\n- wrap\n\n```typescript\npublic wrap\u003cT extends IAnyFn\u003e(\n  func: T,\n  option?: {\n    tag?: string | symbol;\n    inheritPreErr?: boolean;\n    noBlockCurr?: boolean;\n    dropWhenReachLimit?: boolean;\n  }\n)\n```\n\nIf original func is a async function, `wrap` will return `a async function` whose parameters and returned value's type (a.k.a. `Promise`) and value are same as original func.\n\nIf original func is a sync function, `wrap` will return `a async function` whose parameters are the same as the original function, and returned value's promise generic type is the same as original func. Which means it becomes a awaitable async function, besides queueing.\n\n- apply\n\n```typescript\npublic apply\u003cT extends IAnyFn\u003e(\n  func: T,\n  option?: {\n    tag?: string | symbol;\n    inheritPreErr?: boolean;\n    noBlockCurr?: boolean;\n    dropWhenReachLimit?: boolean;\n    args?: Parameters\u003ctypeof func\u003e;\n  }\n)\n```\n\nAlmost the same as `wrap` but just directly executing the original function call, e.g.\n\n```typescript\nconst newFunc = d4c.wrap(asyncFun, { tag: \"queue1\" })\nnewFunc(\"asyncFun_arg1\", \"asyncFun_arg2\");)\n```\n\nbecomes\n\n```typescript\nd4c.apply(asyncFun, { args: ['asyncFun_arg1'], tag: 'queue1' })\n```\n\n## Changelog\n\nCheck [here](https://github.com/grimmer0125/d4c-queue/blob/master/CHANGELOG.md)\n\n## Appendix\n\nI use `babel-node index.js` with the following setting to test.\n\n### babel.config.json\n\n```json\n{\n  \"presets\": [\"@babel/preset-env\"],\n  \"plugins\": [\n    [\n      \"@babel/plugin-proposal-decorators\",\n      {\n        \"legacy\": true\n      }\n    ]\n  ]\n}\n```\n\n### CRACO setting\n\nFollow its site, [CRACO](https://github.com/gsoft-inc/craco).\n\n1. `yarn add @craco/craco`\n2. Replace `react-scripts` with `craco` in `package.json`\n3. `yarn add @babel/preset-env @babel/plugin-proposal-decorators`\n4. Touch `craco.config.js` and modify its content as the following\n5. Then just `yarn start`.\n\n`craco.config.js` (roughly same as `babel.config.json`):\n\n```javascript\nmodule.exports = {\n  babel: {\n    presets: [['@babel/preset-env']],\n    plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]],\n    loaderOptions: {},\n    loaderOptions: (babelLoaderOptions, { env, paths }) =\u003e {\n      return babelLoaderOptions\n    },\n  },\n}\n```\n\n### Angular service example\n\n```typescript\nimport { Injectable } from '@angular/core'\nimport { QConcurrency, concurrent } from 'd4c-queue'\n\n// can be placed below @Injectable, too\n@QConcurrency([{ limit: 1 }])\n@Injectable({\n  providedIn: 'root',\n})\nexport class HeroService {\n  @concurrent\n  async task1() {\n    await wait(5 * 1000)\n  }\n\n  @concurrent\n  async task2() {\n    await wait(1 * 1000)\n  }\n}\n```\n\n### Use latest GitHub code of this library\n\n1. git clone this repo\n2. in cloned project folder, `yarn link`\n3. `yarn test` or `yarn build`\n4. in your project, `yarn link d4c-queue`. Do above ES6/CommonJS import to start to use.\n5. in your project, `yarn unlink d4c-queue` to uninstall.\n\nThe development environment of this library is Node.js v15.14.0 \u0026 Visual Studio Code. TypeScript 4.2.3 is also used and will be automatically installed in node_modules. [typescript-starter](https://github.com/bitjson/typescript-starter) is used to generate two builds, `main` and `module` via its setting. Some example code is in [tests](https://github.com/grimmer0125/d4c-queue/blob/master/src/lib/D4C.spec.ts).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrimmerk%2Fd4c-queue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgrimmerk%2Fd4c-queue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrimmerk%2Fd4c-queue/lists"}