├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.config.ts ├── package.json ├── playground ├── index.ts ├── parallel.ts ├── pipeline.ts └── serial.ts ├── renovate.json ├── src ├── helpers.ts ├── index.ts └── runners │ ├── index.ts │ ├── parallel.ts │ ├── pipeline.ts │ ├── serial.ts │ └── types.ts ├── test ├── base.test.mjs ├── helpers.test.mjs ├── parallel.test.mjs ├── pipeline.test.mjs └── serial.test.mjs ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [{package.json,*.yml,*.cjson}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "standard", 6 | "plugin:prettier/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "plugins": ["@typescript-eslint", "prettier"], 10 | "rules": { 11 | "prettier/prettier": [ 12 | "error", 13 | { 14 | "endOfLine": "auto" 15 | } 16 | ], 17 | "@typescript-eslint/indent": ["error", 2], 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/no-explicit-any": "error" 20 | }, 21 | "overrides": [ 22 | { 23 | "files": ["test/*.test.mjs"], 24 | "extends": [ 25 | "eslint:recommended", 26 | "standard", 27 | "plugin:prettier/recommended" 28 | ], 29 | "plugins": ["prettier"], 30 | "parser": "", 31 | "env": { 32 | "mocha": true 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | node: [14, 16] 19 | 20 | steps: 21 | - uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: checkout 26 | uses: actions/checkout@master 27 | 28 | - name: Install dependencies 29 | run: yarn 30 | 31 | - name: Lint 32 | run: yarn lint 33 | 34 | - name: Test 35 | run: yarn test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | *.log 4 | .DS_Store 5 | coverage 6 | dist 7 | types 8 | .conf* 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "typescript", 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.0.1](https://github.com/ms-fadaei/async-tasks-runner/compare/v0.0.2...v1.0.1) (2021-12-10) 6 | 7 | ### ⚠ BREAKING CHANGES 8 | 9 | * use `TasksRunnerStatus` type instead of `TaskRunnerStatus` 10 | * you need to use `standby` instead of `load` 11 | * You need to use new `create*TasksRunner()` exports 12 | * you need to use `pending` instead of `running` 13 | * `resetTasks` removed. you can use `runner = create*TasksRunner(...runner.tasks)` instead 14 | * use `ParallelTask` & `SerialTask`type instead of `Task` type for `parallel` and `serial` runners 15 | 16 | 17 | ### Features 18 | 19 | * fully type support ([434d4b0](https://github.com/ms-fadaei/async-tasks-runner/commit/434d4b05b3addab86d58197277a374b458606e83)) 20 | * tasks can be added in any position ([c5b0dba](https://github.com/ms-fadaei/async-tasks-runner/commit/c5b0dba0bc859a766d4763b7648fbf5d3ccf5013)) 21 | * add helpers module ([a5da6d4](https://github.com/ms-fadaei/async-tasks-runner/commit/a5da6d41afe746846e48249e03a3af6980ea2a79)) 22 | * add the ability to update tasks even after run ([5eaa3f9](https://github.com/ms-fadaei/async-tasks-runner/commit/5eaa3f99d19b1f90134c9b3d43717d98a8df4e0e)) 23 | * add `getTasksRunnerStatus` helper function ([627cdc2](https://github.com/ms-fadaei/async-tasks-runner/commit/627cdc240830cd29f11ec13d40afc6b91a8bdd81)) 24 | * add renovate config ([527e537](https://github.com/ms-fadaei/async-tasks-runner/commit/527e537633f7cdf5013d5d2c370262450f098814)) 25 | 26 | ### Bug Fixes 27 | 28 | * fix executeCount type ([d4ef5bd](https://github.com/ms-fadaei/async-tasks-runner/commit/d4ef5bde2809e60a6f78f7ee375967f0c3268fde)) 29 | * use strict type for tasks ([845ae89](https://github.com/ms-fadaei/async-tasks-runner/commit/845ae893b8ff367ca15abf83d7fdd5eda370a05d)) 30 | * fix common js bug in importing package ([901e9c1](https://github.com/ms-fadaei/async-tasks-runner/commit/901e9c12a0c599ced91225f72303a821b60d32fa)) 31 | * use conversational naming ([ab71221](https://github.com/ms-fadaei/async-tasks-runner/commit/ab7122176daf04a03eddc2392f1bbc1273673ef6)) 32 | * use named functions instead of classes ([7d07ba9](https://github.com/ms-fadaei/async-tasks-runner/commit/7d07ba912807791d9d13144692f5ff52ea963d37)) 33 | 34 | 35 | ### refactor 36 | 37 | * rename `load` status to `standby` ([2fd713b](https://github.com/ms-fadaei/async-tasks-runner/commit/2fd713b119cd1d9018f923f82ee9295a1f86e3ae)) 38 | * rename `running` status to `pending` ([89abebe](https://github.com/ms-fadaei/async-tasks-runner/commit/89abebe544cb74e1499f65152fe8554283119637)) 39 | * split `NormalTask` type ([d74c394](https://github.com/ms-fadaei/async-tasks-runner/commit/d74c39420ccfa010edd8839ae3448f7d115cd61d)) 40 | * remove `resetTasks` function ([1892230](https://github.com/ms-fadaei/async-tasks-runner/commit/189223004167505f30cf046b6e0c42596f9eaafc)) 41 | 42 | 43 | 44 | ## [0.0.2](https://github.com/ms-fadaei/async-tasks-runner/compare/v0.0.1...v0.0.2) (2021-11-08) 45 | 46 | ### Features 47 | 48 | * add ci ([b8fb1d7](https://github.com/ms-fadaei/async-tasks-runner/commit/b8fb1d7617b7565ecf411d86e9838f34ac4945a8)) 49 | * add more detail on getRunningTask rejection ([8ac119f](https://github.com/ms-fadaei/async-tasks-runner/commit/8ac119f1d201decd4390bf17ebdd09870e0933bf)) 50 | * add playground ([b5d57c1](https://github.com/ms-fadaei/async-tasks-runner/commit/b5d57c1b058d5b1de8a9405cde0e857b438a5991)) 51 | * add project structure ([502ed4f](https://github.com/ms-fadaei/async-tasks-runner/commit/502ed4f1384624f167168460ec3dedc8f0a8e941)) 52 | * add remove(tasks) method to runners ([6662910](https://github.com/ms-fadaei/async-tasks-runner/commit/666291019c38c676d7345e2b8af8331542090884)) 53 | * add reset method ([119d44d](https://github.com/ms-fadaei/async-tasks-runner/commit/119d44d1ffa88a9853df13c9359bfbb5a5ef6836)) 54 | * getRunningTask now return the task always ([53ee9a7](https://github.com/ms-fadaei/async-tasks-runner/commit/53ee9a7c0a4c5b9698762be0ad2e12e17de357c9)) 55 | * init project ([6975963](https://github.com/ms-fadaei/async-tasks-runner/commit/69759634614ca7d0370c84e3105bf0a1b40999f8)) 56 | * pass tasks to class constractor ([77590b5](https://github.com/ms-fadaei/async-tasks-runner/commit/77590b5d7e2d262e9fa4809de036183e892eeb34)) 57 | * use cached firstArg for getRunningTask in pipline ([a248244](https://github.com/ms-fadaei/async-tasks-runner/commit/a24824491e788359803e7c2a0abef62a72d1e01f)) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * remove throwing an error on addTask ([eee0ef6](https://github.com/ms-fadaei/async-tasks-runner/commit/eee0ef625514fe38d60de6db9020bf3fe1c6f3e6)) 63 | * use parrent-like add methode in pipleine ([0cef38e](https://github.com/ms-fadaei/async-tasks-runner/commit/0cef38e85dc4e62a7fc30292de1c36303ff963b8)) 64 | * use promise rejection on absente running task ([12ec4fd](https://github.com/ms-fadaei/async-tasks-runner/commit/12ec4fd369b4857a378ba464b90a1641285a4874)) 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - @ms-fadaei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async Tasks Runner 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![Github Actions CI][github-actions-ci-src]][github-actions-ci-href] 6 | [![License][license-src]][license-href] 7 | 8 | > tiny (~ 1kb gzipped), side-effect free, tree shakable, zero dependencies, and fully typed Tasks Runner. Run your tasks in parallel, serial & pipeline in a more complicated and performant way. 9 | 10 | With this module you can run your tasks in 3 different ways: 11 | 12 | * Parallel: Run your independent tasks in parallel. The result is an array, like the `Promise.allSettled` result. 13 | * Serial: Run your independent tasks in sequence. The result is an array, like the `Promise.allSettled` result. 14 | * Pipeline: Run your dependent tasks in sequence. The result of every task will be passed to the next task. 15 | 16 | ![Async Tasks Runner](https://user-images.githubusercontent.com/54557683/140879231-30adf612-9149-4a10-af9f-6af094fa9152.jpg) 17 | 18 | 19 | 20 | ⚠️ Attention: This package comes without any polyfills. If you want to support an older environment, please polyfills it depending on what you need. 21 | * ES2015 22 | * ES2017 async/await 23 | * ES2020 Promise.allSettled (Just for the ParallelTasksRunner) 24 | 25 | environment support 26 | 27 | 28 | ## Setup 29 | 30 | 1. Add `async-tasks-runner` dependency with `yarn` or `npm` to your project 31 | 32 | ```bash 33 | yarn add async-tasks-runner 34 | 35 | # or 36 | 37 | npm install async-tasks-runner 38 | ``` 39 | 40 | 2. Use it everywhere you want 41 | ```js 42 | // ES Module 43 | import {createParallelTasksRunner, runParallelTasks, getParallelTasks} from "async-tasks-runner" 44 | 45 | // CJS 46 | const {createParallelTasksRunner, runParallelTasks, getParallelTasks} = require("async-tasks-runner") 47 | ``` 48 | 49 | ## Runners 50 | 51 | | Runner | Example | 52 | | ------ | ------- | 53 | | ParallelTasksRunner | See [Examples](#parallel-tasks-runner) | 54 | | SerialTasksRunner | See [Examples](#serial-tasks-runner) | 55 | | PipelineTasksRunner | See [Examples](#pipeline-tasks-runner) | 56 | 57 | ## Global Helper Functions 58 | 59 | | Functions | Description| Parameter(s) | Return Value | 60 | | ------ | ------- | ------ | ------- | 61 | | pushTasks | push tasks to the runner. [See pushTasks Section](#pushTasks) | taskRunnerObject, tasks[] | number: the new length of tasks | 62 | | spliceTasks | removing or replacing existing tasks and/or adding new elements in place [See spliceTasks Section](#spliceTasks) | taskRunnerObject, startIndex, deleteCount, newTasks[] | array: list of removed tasks | 63 | | getTasksRunnerStatus | current status of the runner. [See getTasksRunnerStatus Section](#getTasksRunnerStatus) | taskRunnerObject | string: status | 64 | 65 | ### pushTasks 66 | With this function, you can push new tasks to the runner (like `Array.prototype.push()`). 67 | ```js 68 | pushTasks(taskRunnerObject, ...newTasks[]); 69 | ``` 70 | 71 | ### spliceTasks 72 | With this function, you can change the contents of an array by removing or replacing existing elements and/or adding new elements in place (like `Array.prototype.splice()`). 73 | ```js 74 | spliceTasks(taskRunnerObject, start, deleteCount, ...newTasks[] ); 75 | ``` 76 | 77 | ### getTasksRunnerStatus 78 | With this function, you can get the current status of the runner. 79 | ```js 80 | const currentStatus = getTasksRunnerStatus(taskRunnerObject) 81 | ``` 82 | 83 | This function returns 4 different statuses: 84 | 1. `standby`: The runner is open to adding or removing tasks. This status actually present that the runner is not started yet! 85 | 2. `pending`: Represent that the runner starts pending the tasks and in the meantime, you can't nor add/remove tasks to/from the runner neither reset the runner. 86 | 3. `fulfilled`: Represent that the runner did its job and now you can get the result or reset the runner to add/remove tasks to/from the runner and run them again. 87 | 4. `rejected`: In this status, the runner did its job but with an error in the process, and the whole run is rejected. Like `fulfilled` status, you can reset the runner to add/remove tasks to/from the runner and run them again. 88 | 89 | ## Examples 90 | 91 | ### Parallel Tasks Runner 92 | You can run your tasks in parallel. the result is an array, like the `Promise.allSettled` result. 93 | 94 | #### Create 95 | You can create a parallel task runner object by the calling of `createParallelTaskRunner`. The result will be an object that must be passed as the first argument to any helper functions that you want to use. 96 | 97 | ```js 98 | import {createParallelTasksRunner} from "async-tasks-runner" 99 | const taskRunnerObject = createParallelTasksRunner(...tasks); 100 | ``` 101 | 102 | Every task in the `ParallelTasksRunner` must be a function without a parameter and return a promise. 103 | 104 | ```js 105 | const url = "https://google.com"; 106 | 107 | const task1 = () => { 108 | return fetch(`${url}/first`); 109 | } 110 | 111 | const task2 = () => { 112 | return fetch(`${url}/second`); 113 | } 114 | 115 | const taskRunnerObject = createParallelTasksRunner(task1, task2); 116 | ``` 117 | 118 | #### Run 119 | Start pending the runner. When it is called, the status changed to `pending` and when it is done, the status changed to `fulfilled`. If you run it again, it's not starting to run again and fulfilled with the previous result unless you reset the runner. This means you can start the runner then do some heavy work and call it again to get the result without getting the process blocked! This method returns a promise that resolves after all of the given promises have either been fulfilled or rejected, with an array of objects that each describes the outcome of each promise. 120 | 121 | ```js 122 | import {runParallelTasks} from "async-tasks-runner" 123 | const result = await runParallelTasks(taskRunnerObject); 124 | 125 | // [ 126 | // {status: "fulfilled", value: 33}, 127 | // {status: "fulfilled", value: 66}, 128 | // {status: "fulfilled", value: 99}, 129 | // {status: "rejected", reason: Error: an error} 130 | // ] 131 | ``` 132 | 133 | #### Result of Specific Task 134 | After running the runner (no matter of status is `pending` or `fulfilled` or `rejected`), you can access a specific task with this method. The only parameter of this method is the index of the task. This method returns a Promise that resolves with the task result or is rejected with the current or previous task rejection. 135 | 136 | ```js 137 | import {getParallelTasks} from "async-tasks-runner" 138 | const result = await getParallelTasks(taskRunnerObject, 0); 139 | ``` 140 | 141 | #### Clone 142 | You can clone tasks of your runner and create a new tasks runner. 143 | 144 | ```js 145 | import {createParallelTasksRunner} from "async-tasks-runner" 146 | const newTaskRunnerObject = createParallelTasksRunner(...taskRunnerObject.tasks); 147 | ``` 148 | 149 | ### Serial Tasks Runner 150 | You can run your tasks in sequence. the result is an array, like the `Promise.allSettled` result. 151 | 152 | #### Create 153 | You can create a serial task runner object by the calling of `createSerialTaskRunner`. The result will be an object that must be passed as the first argument to any helper functions that you want to use. 154 | 155 | ```js 156 | import {createSerialTasksRunner} from "async-tasks-runner" 157 | const taskRunnerObject = createSerialTasksRunner(...tasks); 158 | ``` 159 | 160 | Every task in the `SerialTasksRunner` must be a function without a parameter and return a promise. 161 | 162 | ```js 163 | const url = "https://google.com"; 164 | 165 | const task1 = () => { 166 | return fetch(`${url}/first`); 167 | } 168 | 169 | const task2 = () => { 170 | return fetch(`${url}/second`); 171 | } 172 | 173 | const taskRunnerObject = createSerialTasksRunner(task1, task2); 174 | ``` 175 | 176 | #### Run 177 | Start pending the runner. When it is called, the status changed to `pending` and when it is done, the status changed to `fulfilled`. If you run it again, it's not starting to run again and fulfilled with the previous result unless you reset the runner. This means you can start the runner then do some heavy work and call it again to get the result without getting the process blocked! This method returns a promise that resolves after all of the given promises have either been fulfilled or rejected, with an array of objects that each describes the outcome of each promise. 178 | 179 | ```js 180 | import {runSerialTasks} from "async-tasks-runner" 181 | const result = await runSerialTasks(taskRunnerObject); 182 | 183 | // [ 184 | // {status: "fulfilled", value: 33}, 185 | // {status: "fulfilled", value: 66}, 186 | // {status: "fulfilled", value: 99}, 187 | // {status: "rejected", reason: Error: an error} 188 | // ] 189 | ``` 190 | 191 | #### Result of Specific Task 192 | After running the runner (no matter of status is `pending` or `fulfilled` or `rejected`), you can access a specific task with this method. The only parameter of this method is the index of the task. This method returns a Promise that resolves with the task result or is rejected with the current or previous task rejection. 193 | 194 | ```js 195 | import {getSerialTasks} from "async-tasks-runner" 196 | const result = await getSerialTasks(taskRunnerObject, 0); 197 | ``` 198 | 199 | #### Clone 200 | You can clone tasks of your runner and create a new tasks runner. 201 | 202 | ```js 203 | import {createSerialTasksRunner} from "async-tasks-runner" 204 | const newTaskRunnerObject = createSerialTasksRunner(...taskRunnerObject.tasks); 205 | ``` 206 | 207 | ### Pipeline Tasks Runner 208 | You can run your tasks in sequence. The result of the currently pending task will be the argument of the next task. 209 | 210 | #### Create 211 | You can create a serial task runner object by the calling of `createPipelineTaskRunner`. The result will be an object that must be passed as the first argument to any helper functions that you want to use. 212 | 213 | ```js 214 | import {createPipelineTasksRunner} from "async-tasks-runner" 215 | const taskRunnerObject = createPipelineTasksRunner(...tasks); 216 | ``` 217 | 218 | Every task in the `PipelineTasksRunner` must be a function that returns a promise. This function can accept one parameter that will be filled with the previous fulfilled task result. 219 | 220 | ```js 221 | const url = "https://google.com"; 222 | 223 | const task1 = (code) => { 224 | return fetch(`${url}/${code}`); 225 | } 226 | 227 | const task2 = (data) => { 228 | return fetch(data.url); 229 | } 230 | 231 | const taskRunnerObject = createPipelineTasksRunner(task1, task2); 232 | ``` 233 | 234 | #### Run 235 | With this method, you can start pending the tasks. When it is called, the status changed to `pending` and when it is done, the status changed to `fulfilled` if all tasks run successfully or `rejected` if one of the tasks in the list get failed (and the next tasks don't get run). If you run it again, it's not starting to run again and `fulfilled` or `rejected` with the previous result unless you reset the runner. This means you can start the runner then do some heavy work and call it again to get the result without getting the process blocked! This method always returns a promise that resolves with the final task result or rejects with an error. The `runPipelineTasks` function needs an extra argument for the parameter of the first task function (initial parameter). 236 | 237 | ```js 238 | import {runPipelineTasks} from "async-tasks-runner" 239 | const result = await runPipelineTasks(taskRunnerObject, firstArgument); 240 | 241 | // [ 242 | // {status: "fulfilled", value: 33}, 243 | // {status: "fulfilled", value: 66}, 244 | // {status: "fulfilled", value: 99}, 245 | // {status: "rejected", reason: Error: an error} 246 | // ] 247 | ``` 248 | 249 | #### Result of Specific Task 250 | After running the runner (no matter of status is `pending` or `fulfilled` or `rejected`), you can access a specific task with this method. The only parameter of this method is an index of the task. This method returns a Promise that resolves with the task result or is rejected with the current or previous task failure error. 251 | 252 | ```js 253 | import {getPipelineTasks} from "async-tasks-runner" 254 | const result = await getPipelineTasks(taskRunnerObject, 0); 255 | ``` 256 | 257 | #### Clone 258 | You can clone tasks of your runner and create a new tasks runner. 259 | 260 | ```js 261 | import {createPipelineTasksRunner} from "async-tasks-runner" 262 | const newTaskRunnerObject = createPipelineTasksRunner(...taskRunnerObject.tasks); 263 | ``` 264 | 265 | ## Extra Helper Functions 266 | 267 | | Functions | Description| Parameter(s) | Return Value | 268 | | ------ | ------- | ------ | ------- | 269 | | createTimeoutResolve | create a delay with resolve | timeout: number, response: T, cancelToken?: Promise | promise | 270 | | createTimeoutReject | create a delay with reject | timeout: number, response: T, cancelToken?: Promise | promise | 271 | | createTaskWithTimeout | create a task with timeout | task: Task, timeout: number | Task 272 | 273 | ## Contribution 274 | 275 | 1. Fork this repository 276 | 2. Install dependencies using `yarn install` or `npm install` 277 | 3. Making your changes 278 | 4. Run the `yarn lint` command 279 | 5. Run the `yarn test` command 280 | 6. Push and make a PR 281 | 282 | ## License 283 | 284 | [MIT License](./LICENSE) 285 | 286 | Copyright (c) Mohammad Saleh Fadaei ([@ms-fadaei](https://github.com/ms-fadaei)) 287 | 288 | 289 | 290 | [npm-version-src]: https://img.shields.io/npm/v/async-tasks-runner/latest.svg 291 | [npm-version-href]: https://npmjs.com/package/async-tasks-runner 292 | 293 | [npm-downloads-src]: https://img.shields.io/npm/dt/async-tasks-runner.svg 294 | [npm-downloads-href]: https://npmjs.com/package/async-tasks-runner 295 | 296 | [github-actions-ci-src]: https://github.com/ms-fadaei/async-tasks-runner/workflows/ci/badge.svg 297 | [github-actions-ci-href]: https://github.com/ms-fadaei/async-tasks-runner/actions?query=workflow%3Aci 298 | 299 | [license-src]: https://img.shields.io/npm/l/async-tasks-runner.svg 300 | [license-href]: https://npmjs.com/package/async-tasks-runner 301 | -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | declaration: true, 5 | emitCJS: true, 6 | entries: ['src/index', 'src/helpers'], 7 | }); 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-tasks-runner", 3 | "version": "1.0.1", 4 | "description": "Run tasks in parallel, serial & pipeline.", 5 | "repository": "ms-fadaei/async-tasks-runner", 6 | "license": "MIT", 7 | "keywords": [ 8 | "async", 9 | "task", 10 | "parallel", 11 | "serial", 12 | "pipeline" 13 | ], 14 | "author": { 15 | "name": "Mohammad Saleh Fadaei", 16 | "email": "ms.fadaei1997@gmail.com", 17 | "url": "https://twitter.com/ms_fadaei" 18 | }, 19 | "sideEffects": false, 20 | "type": "module", 21 | "exports": { 22 | ".": { 23 | "import": "./dist/index.mjs", 24 | "require": "./dist/index.cjs" 25 | }, 26 | "./helpers": { 27 | "import": "./dist/helpers.mjs", 28 | "require": "./dist/helpers.cjs" 29 | } 30 | }, 31 | "main": "./dist/index.cjs", 32 | "module": "./dist/index.mjs", 33 | "types": "./dist/index.d.ts", 34 | "files": [ 35 | "dist" 36 | ], 37 | "scripts": { 38 | "build": "unbuild", 39 | "prepack": "yarn build", 40 | "lint": "eslint --ext .ts,mjs --ignore-path .gitignore .", 41 | "play": "jiti playground/index.ts", 42 | "release": "yarn test && npx standard-version && git push --follow-tags && npm publish", 43 | "test": "yarn build && mocha ./test/*.test.*" 44 | }, 45 | "lint-staged": { 46 | "*.ts": "eslint --fix", 47 | "*.test.mjs": "eslint --fix" 48 | }, 49 | "dependencies": {}, 50 | "devDependencies": { 51 | "@nuxtjs/eslint-config-typescript": "8.0.0", 52 | "@types/flat": "latest", 53 | "@types/mocha": "9.1.0", 54 | "@types/node": "latest", 55 | "@typescript-eslint/eslint-plugin": "5.10.1", 56 | "@typescript-eslint/parser": "5.10.1", 57 | "chai": "4.3.4", 58 | "chai-as-promised": "7.1.1", 59 | "eslint": "8.7.0", 60 | "eslint-config-prettier": "8.3.0", 61 | "eslint-config-standard": "16.0.3", 62 | "eslint-plugin-import": "2.25.4", 63 | "eslint-plugin-node": "11.1.0", 64 | "eslint-plugin-prettier": "4.0.0", 65 | "eslint-plugin-promise": "6.0.0", 66 | "husky": "7.0.4", 67 | "jiti": "latest", 68 | "lint-staged": "12.3.1", 69 | "mocha": "9.2.0", 70 | "prettier": "2.5.1", 71 | "standard-version": "latest", 72 | "typescript": "latest", 73 | "unbuild": "latest" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /playground/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { createTimeoutResolve, createTimeoutReject } from '../src/helpers'; 3 | import { Task } from '../src/index'; 4 | // import { _runParallelTasks } from './parallel' 5 | import { _runSerialTasks } from './serial'; 6 | // import { _runPipelineTasks } from './pipeline' 7 | 8 | (async () => { 9 | const exampleTasks: Task[] = [ 10 | (arg = 0) => createTimeoutResolve(8, 8 + arg), 11 | (arg = 0) => createTimeoutResolve(2, 2 + arg), 12 | (arg = 0) => createTimeoutResolve(6, 6 + arg), 13 | (arg = 0) => createTimeoutResolve(16, 16 + arg), 14 | (arg = 0) => createTimeoutResolve(12, 12 + arg), 15 | (arg = 0) => createTimeoutReject(4, 4 + arg), 16 | (arg = 0) => createTimeoutResolve(18, 18 + arg), 17 | (arg = 0) => createTimeoutResolve(14, 14 + arg), 18 | (arg = 0) => createTimeoutResolve(10, 10 + arg), 19 | (arg = 0) => createTimeoutReject(20, 20 + arg), 20 | ]; 21 | 22 | // console.log('Parallel Tasks:\n') 23 | // await _runParallelTasks(...exampleTasks) 24 | 25 | console.log('Serial Tasks:\n'); 26 | await _runSerialTasks(...exampleTasks); 27 | 28 | // console.log('Pipeline Tasks:\n') 29 | // await _runPipelineTasks(...exampleTasks) 30 | })(); 31 | -------------------------------------------------------------------------------- /playground/parallel.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createParallelTasksRunner, runParallelTasks, getParallelTasks, pushTasks, spliceTasks } from '../src/index'; 3 | 4 | export async function _runParallelTasks(...tasks) { 5 | // 1. Create the runner with two first tasks (blocking result) 6 | console.log('1. Create the runner with two first tasks (blocking result)'); 7 | let runner = createParallelTasksRunner(tasks[0], tasks[1]); 8 | 9 | // 1.1. Run the runner with task 8 and 2 10 | { 11 | console.log('\n\n1.1. Run the runner with task 8 and 2'); 12 | console.time('parallel-run-time'); 13 | const result = await runParallelTasks(runner).catch((e) => `!!!ERROR: ${e}`); 14 | console.timeEnd('parallel-run-time'); 15 | console.log('parallel-result', result); 16 | } 17 | 18 | // 1.2. Push task 6 and run it again 19 | { 20 | console.log('\n\n1.2. Push task 6 and run it again'); 21 | pushTasks(runner, tasks[2]); 22 | console.time('parallel-run-time'); 23 | const result = await runParallelTasks(runner).catch((e) => `!!!ERROR: ${e}`); 24 | console.timeEnd('parallel-run-time'); 25 | console.log('parallel-result', result); 26 | } 27 | 28 | // 1.3. Push task 16 and run it again 29 | { 30 | console.log('\n\n1.3. Push task 16 and run it again'); 31 | pushTasks(runner, tasks[3]); 32 | console.time('parallel-run-time'); 33 | const result = await runParallelTasks(runner).catch((e) => `!!!ERROR: ${e}`); 34 | console.timeEnd('parallel-run-time'); 35 | console.log('parallel-result', result); 36 | } 37 | 38 | // 1.4. Remove task 16 and insert 12 abd 4 instead 39 | { 40 | console.log('\n\n1.4. Remove task 16 and insert 12 abd 4 instead'); 41 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 42 | console.time('parallel-run-time'); 43 | const result = await runParallelTasks(runner).catch((e) => `!!!ERROR: ${e}`); 44 | console.timeEnd('parallel-run-time'); 45 | console.log('parallel-result', result); 46 | } 47 | 48 | // 1.5. Remove all tasks and insert 18, 14, 10, 20 49 | { 50 | console.log('\n\n1.5. Remove all tasks and insert 18, 14, 10, 20'); 51 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 52 | console.time('parallel-run-time'); 53 | const result = await runParallelTasks(runner).catch((e) => `!!!ERROR: ${e}`); 54 | console.timeEnd('parallel-run-time'); 55 | console.log('parallel-result', result); 56 | } 57 | 58 | // 1.6. get task 10 59 | { 60 | console.log('\n\n1.6. get task 10'); 61 | console.time('parallel-run-time'); 62 | const result = await getParallelTasks(runner, 2).catch((e) => `!!!ERROR: ${e}`); 63 | console.timeEnd('parallel-run-time'); 64 | console.log('parallel-result', result); 65 | } 66 | 67 | // 1.7. get task 20 (rejected task) 68 | { 69 | console.log('\n\n1.7. get task 20 (rejected task)'); 70 | console.time('parallel-run-time'); 71 | const result = await getParallelTasks(runner, 3).catch((e) => `!!!ERROR: ${e}`); 72 | console.timeEnd('parallel-run-time'); 73 | console.log('parallel-result', result); 74 | } 75 | 76 | // 2. Create the runner with two first tasks (non-blocking result) 77 | console.log('\n\n\n2. Create the runner with two first tasks (non-blocking result)'); 78 | runner = createParallelTasksRunner(tasks[0], tasks[1]); 79 | 80 | // 2.1. Run the runner with task 8 and 2 81 | console.time('parallel-run-time'); 82 | runParallelTasks(runner) 83 | .catch((e) => `!!!ERROR: ${e}`) 84 | .then((result) => { 85 | console.log('\n\n2.1. Run the runner with task 8 and 2'); 86 | console.timeEnd('parallel-run-time'); 87 | console.log('parallel-result', result); 88 | }); 89 | 90 | // 2.2. Push task 6 and run it again 91 | pushTasks(runner, tasks[2]); 92 | console.time('parallel-run-time-1'); 93 | runParallelTasks(runner) 94 | .catch((e) => `!!!ERROR: ${e}`) 95 | .then((result) => { 96 | console.log('\n\n2.2. Push task 6 and run it again'); 97 | console.timeEnd('parallel-run-time-1'); 98 | console.log('parallel-result', result); 99 | }); 100 | 101 | // 2.3. Push task 16 and run it again 102 | pushTasks(runner, tasks[3]); 103 | console.time('parallel-run-time-2'); 104 | runParallelTasks(runner) 105 | .catch((e) => `!!!ERROR: ${e}`) 106 | .then((result) => { 107 | console.log('\n\n2.3. Push task 16 and run it again'); 108 | console.timeEnd('parallel-run-time-2'); 109 | console.log('parallel-result', result); 110 | }); 111 | 112 | // 2.4. Remove task 16 and insert 12 abd 4 instead 113 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 114 | console.time('parallel-run-time-3'); 115 | runParallelTasks(runner) 116 | .catch((e) => `!!!ERROR: ${e}`) 117 | .then((result) => { 118 | console.log('\n\n2.4. Remove task 16 and insert 12 abd 4 instead'); 119 | console.timeEnd('parallel-run-time-3'); 120 | console.log('parallel-result', result); 121 | }); 122 | 123 | // 2.5. Remove all tasks and insert 18, 14, 10, 20 124 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 125 | console.time('parallel-run-time-4'); 126 | runParallelTasks(runner) 127 | .catch((e) => `!!!ERROR: ${e}`) 128 | .then((result) => { 129 | console.log('\n\n2.5. Remove all tasks and insert 18, 14, 10, 20'); 130 | console.timeEnd('parallel-run-time-4'); 131 | console.log('parallel-result', result); 132 | }); 133 | 134 | // 2.6. get task 10 135 | console.time('parallel-run-time-5'); 136 | getParallelTasks(runner, 2) 137 | .catch((e) => `!!!ERROR: ${e}`) 138 | .then((result) => { 139 | console.log('\n\n2.6. get task 10'); 140 | console.timeEnd('parallel-run-time-5'); 141 | console.log('parallel-result', result); 142 | }); 143 | 144 | // 2.7. get task 20 (rejected task) 145 | console.time('parallel-run-time-6'); 146 | getParallelTasks(runner, 3) 147 | .catch((e) => `!!!ERROR: ${e}`) 148 | .then((result) => { 149 | console.log('\n\n2.7. get task 20 (rejected task)'); 150 | console.timeEnd('parallel-run-time-6'); 151 | console.log('parallel-result', result); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /playground/pipeline.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createPipelineTasksRunner, runPipelineTasks, getPipelineTasks, pushTasks, spliceTasks } from '../src/index'; 3 | 4 | export async function _runPipelineTasks(...tasks) { 5 | // 1. Create the runner with two first tasks (blocking result) 6 | console.log('1. Create the runner with two first tasks (blocking result)'); 7 | let runner = createPipelineTasksRunner(tasks[0], tasks[1]); 8 | 9 | // 1.1. Run the runner with task 8 and 2 10 | { 11 | console.log('\n\n1.1. Run the runner with task 8 and 2'); 12 | console.time('pipeline-run-time'); 13 | const result = await runPipelineTasks(runner, 0).catch((e) => `!!!ERROR: ${e}`); 14 | console.timeEnd('pipeline-run-time'); 15 | console.log('pipeline-result', result); 16 | } 17 | 18 | // 1.2. Push task 6 and run it again 19 | { 20 | console.log('\n\n1.2. Push task 6 and run it again'); 21 | pushTasks(runner, tasks[2]); 22 | console.time('pipeline-run-time'); 23 | const result = await runPipelineTasks(runner, 0).catch((e) => `!!!ERROR: ${e}`); 24 | console.timeEnd('pipeline-run-time'); 25 | console.log('pipeline-result', result); 26 | } 27 | 28 | // 1.3. Push task 16 and run it again 29 | { 30 | console.log('\n\n1.3. Push task 16 and run it again'); 31 | pushTasks(runner, tasks[3]); 32 | console.time('pipeline-run-time'); 33 | const result = await runPipelineTasks(runner, 0).catch((e) => `!!!ERROR: ${e}`); 34 | console.timeEnd('pipeline-run-time'); 35 | console.log('pipeline-result', result); 36 | } 37 | 38 | // 1.4. Remove task 16 and insert 12 and 4 instead 39 | { 40 | console.log('\n\n1.4. Remove task 16 and insert 12 and 4 instead'); 41 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 42 | console.time('pipeline-run-time'); 43 | const result = await runPipelineTasks(runner, 0).catch((e) => `!!!ERROR: ${e}`); 44 | console.timeEnd('pipeline-run-time'); 45 | console.log('pipeline-result', result); 46 | } 47 | 48 | // 1.5. Remove all tasks and insert 18, 14, 10, 20 49 | { 50 | console.log('\n\n1.5. Remove all tasks and insert 18, 14, 10, 20'); 51 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 52 | console.time('pipeline-run-time'); 53 | const result = await runPipelineTasks(runner, 0).catch((e) => `!!!ERROR: ${e}`); 54 | console.timeEnd('pipeline-run-time'); 55 | console.log('pipeline-result', result); 56 | } 57 | 58 | // 1.6. get task 10 59 | { 60 | console.log('\n\n1.6. get task 10'); 61 | console.time('pipeline-run-time'); 62 | const result = await getPipelineTasks(runner, 2).catch((e) => `!!!ERROR: ${e}`); 63 | console.timeEnd('pipeline-run-time'); 64 | console.log('pipeline-result', result); 65 | } 66 | 67 | // 1.7. get task 20 (rejected task) 68 | { 69 | console.log('\n\n1.7. get task 20 (rejected task)'); 70 | console.time('pipeline-run-time'); 71 | const result = await getPipelineTasks(runner, 3).catch((e) => `!!!ERROR: ${e}`); 72 | console.timeEnd('pipeline-run-time'); 73 | console.log('pipeline-result', result); 74 | } 75 | 76 | // 2. Create the runner with two first tasks (non-blocking result) 77 | console.log('\n\n\n2. Create the runner with two first tasks (non-blocking result)'); 78 | runner = createPipelineTasksRunner(tasks[0], tasks[1]); 79 | 80 | // 2.1. Run the runner with task 8 and 2 81 | console.time('pipeline-run-time'); 82 | runPipelineTasks(runner, 0) 83 | .catch((e) => `!!!ERROR: ${e}`) 84 | .then((result) => { 85 | console.log('\n\n2.1. Run the runner with task 8 and 2'); 86 | console.timeEnd('pipeline-run-time'); 87 | console.log('pipeline-result', result); 88 | }); 89 | 90 | // 2.2. Push task 6 and run it again 91 | pushTasks(runner, tasks[2]); 92 | console.time('pipeline-run-time-1'); 93 | runPipelineTasks(runner, 0) 94 | .catch((e) => `!!!ERROR: ${e}`) 95 | .then((result) => { 96 | console.log('\n\n2.2. Push task 6 and run it again'); 97 | console.timeEnd('pipeline-run-time-1'); 98 | console.log('pipeline-result', result); 99 | }); 100 | 101 | // 2.3. Push task 16 and run it again 102 | pushTasks(runner, tasks[3]); 103 | console.time('pipeline-run-time-2'); 104 | runPipelineTasks(runner, 0) 105 | .catch((e) => `!!!ERROR: ${e}`) 106 | .then((result) => { 107 | console.log('\n\n2.3. Push task 16 and run it again'); 108 | console.timeEnd('pipeline-run-time-2'); 109 | console.log('pipeline-result', result); 110 | }); 111 | 112 | // 2.4. Remove task 16 and insert 12 and 4 instead 113 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 114 | console.time('pipeline-run-time-3'); 115 | runPipelineTasks(runner, 0) 116 | .catch((e) => `!!!ERROR: ${e}`) 117 | .then((result) => { 118 | console.log('\n\n2.4. Remove task 16 and insert 12 and 4 instead'); 119 | console.timeEnd('pipeline-run-time-3'); 120 | console.log('pipeline-result', result); 121 | }); 122 | 123 | // 2.5. Remove all tasks and insert 18, 14, 10, 20 124 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 125 | console.time('pipeline-run-time-4'); 126 | runPipelineTasks(runner, 0) 127 | .catch((e) => `!!!ERROR: ${e}`) 128 | .then((result) => { 129 | console.log('\n\n2.5. Remove all tasks and insert 18, 14, 10, 20'); 130 | console.timeEnd('pipeline-run-time-4'); 131 | console.log('pipeline-result', result); 132 | }); 133 | 134 | // 2.6. get task 10 135 | console.time('pipeline-run-time-5'); 136 | getPipelineTasks(runner, 2) 137 | .catch((e) => `!!!ERROR: ${e}`) 138 | .then((result) => { 139 | console.log('\n\n2.6. get task 10'); 140 | console.timeEnd('pipeline-run-time-5'); 141 | console.log('pipeline-result', result); 142 | }); 143 | 144 | // 2.7. get task 20 (rejected task) 145 | console.time('pipeline-run-time-6'); 146 | getPipelineTasks(runner, 3) 147 | .catch((e) => `!!!ERROR: ${e}`) 148 | .then((result) => { 149 | console.log('\n\n2.7. get task 20 (rejected task)'); 150 | console.timeEnd('pipeline-run-time-6'); 151 | console.log('pipeline-result', result); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /playground/serial.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { createSerialTasksRunner, runSerialTasks, getSerialTasks, pushTasks, spliceTasks } from '../src/index'; 3 | 4 | export async function _runSerialTasks(...tasks) { 5 | // 1. Create the runner with two first tasks (blocking result) 6 | console.log('1. Create the runner with two first tasks (blocking result)'); 7 | let runner = createSerialTasksRunner(tasks[0], tasks[1]); 8 | 9 | // 1.1. Run the runner with task 8 and 2 10 | { 11 | console.log('\n\n1.1. Run the runner with task 8 and 2'); 12 | console.time('serial-run-time'); 13 | const result = await runSerialTasks(runner).catch((e) => `!!!ERROR: ${e}`); 14 | console.timeEnd('serial-run-time'); 15 | console.log('serial-result', result); 16 | } 17 | 18 | // 1.2. Push task 6 and run it again 19 | { 20 | console.log('\n\n1.2. Push task 6 and run it again'); 21 | pushTasks(runner, tasks[2]); 22 | console.time('serial-run-time'); 23 | const result = await runSerialTasks(runner).catch((e) => `!!!ERROR: ${e}`); 24 | console.timeEnd('serial-run-time'); 25 | console.log('serial-result', result); 26 | } 27 | 28 | // 1.3. Push task 16 and run it again 29 | { 30 | console.log('\n\n1.3. Push task 16 and run it again'); 31 | pushTasks(runner, tasks[3]); 32 | console.time('serial-run-time'); 33 | const result = await runSerialTasks(runner).catch((e) => `!!!ERROR: ${e}`); 34 | console.timeEnd('serial-run-time'); 35 | console.log('serial-result', result); 36 | } 37 | 38 | // 1.4. Remove task 16 and insert 12 abd 4 instead 39 | { 40 | console.log('\n\n1.4. Remove task 16 and insert 12 abd 4 instead'); 41 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 42 | console.time('serial-run-time'); 43 | const result = await runSerialTasks(runner).catch((e) => `!!!ERROR: ${e}`); 44 | console.timeEnd('serial-run-time'); 45 | console.log('serial-result', result); 46 | } 47 | 48 | // 1.5. Remove all tasks and insert 18, 14, 10, 20 49 | { 50 | console.log('\n\n1.5. Remove all tasks and insert 18, 14, 10, 20'); 51 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 52 | console.time('serial-run-time'); 53 | const result = await runSerialTasks(runner).catch((e) => `!!!ERROR: ${e}`); 54 | console.timeEnd('serial-run-time'); 55 | console.log('serial-result', result); 56 | } 57 | 58 | // 1.6. get task 10 59 | { 60 | console.log('\n\n1.6. get task 10'); 61 | console.time('serial-run-time'); 62 | const result = await getSerialTasks(runner, 2).catch((e) => `!!!ERROR: ${e}`); 63 | console.timeEnd('serial-run-time'); 64 | console.log('serial-result', result); 65 | } 66 | 67 | // 1.7. get task 20 (rejected task) 68 | { 69 | console.log('\n\n1.7. get task 20 (rejected task)'); 70 | console.time('serial-run-time'); 71 | const result = await getSerialTasks(runner, 3).catch((e) => `!!!ERROR: ${e}`); 72 | console.timeEnd('serial-run-time'); 73 | console.log('serial-result', result); 74 | } 75 | 76 | // 2. Create the runner with two first tasks (non-blocking result) 77 | console.log('\n\n\n2. Create the runner with two first tasks (non-blocking result)'); 78 | runner = createSerialTasksRunner(tasks[0], tasks[1]); 79 | 80 | // 2.1. Run the runner with task 8 and 2 81 | console.time('serial-run-time'); 82 | runSerialTasks(runner) 83 | .catch((e) => `!!!ERROR: ${e}`) 84 | .then((result) => { 85 | console.log('\n\n2.1. Run the runner with task 8 and 2'); 86 | console.timeEnd('serial-run-time'); 87 | console.log('serial-result', result); 88 | }); 89 | 90 | // 2.2. Push task 6 and run it again 91 | pushTasks(runner, tasks[2]); 92 | console.time('serial-run-time-1'); 93 | runSerialTasks(runner) 94 | .catch((e) => `!!!ERROR: ${e}`) 95 | .then((result) => { 96 | console.log('\n\n2.2. Push task 6 and run it again'); 97 | console.timeEnd('serial-run-time-1'); 98 | console.log('serial-result', result); 99 | }); 100 | 101 | // 2.3. Push task 16 and run it again 102 | pushTasks(runner, tasks[3]); 103 | console.time('serial-run-time-2'); 104 | runSerialTasks(runner) 105 | .catch((e) => `!!!ERROR: ${e}`) 106 | .then((result) => { 107 | console.log('\n\n2.3. Push task 16 and run it again'); 108 | console.timeEnd('serial-run-time-2'); 109 | console.log('serial-result', result); 110 | }); 111 | 112 | // 2.4. Remove task 16 and insert 12 abd 4 instead 113 | spliceTasks(runner, 3, 1, tasks[4], tasks[5]); 114 | console.time('serial-run-time-3'); 115 | runSerialTasks(runner) 116 | .catch((e) => `!!!ERROR: ${e}`) 117 | .then((result) => { 118 | console.log('\n\n2.4. Remove task 16 and insert 12 abd 4 instead'); 119 | console.timeEnd('serial-run-time-3'); 120 | console.log('serial-result', result); 121 | }); 122 | 123 | // 2.5. Remove all tasks and insert 18, 14, 10, 20 124 | spliceTasks(runner, 0, 5, tasks[6], tasks[7], tasks[8], tasks[9]); 125 | console.time('serial-run-time-4'); 126 | runSerialTasks(runner) 127 | .catch((e) => `!!!ERROR: ${e}`) 128 | .then((result) => { 129 | console.log('\n\n2.5. Remove all tasks and insert 18, 14, 10, 20'); 130 | console.timeEnd('serial-run-time-4'); 131 | console.log('serial-result', result); 132 | }); 133 | 134 | // 2.6. get task 10 135 | console.time('serial-run-time-5'); 136 | getSerialTasks(runner, 2) 137 | .catch((e) => `!!!ERROR: ${e}`) 138 | .then((result) => { 139 | console.log('\n\n2.6. get task 10'); 140 | console.timeEnd('serial-run-time-5'); 141 | console.log('serial-result', result); 142 | }); 143 | 144 | // 2.7. get task 20 (rejected task) 145 | console.time('serial-run-time-6'); 146 | getSerialTasks(runner, 3) 147 | .catch((e) => `!!!ERROR: ${e}`) 148 | .then((result) => { 149 | console.log('\n\n2.7. get task 20 (rejected task)'); 150 | console.timeEnd('serial-run-time-6'); 151 | console.log('serial-result', result); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Task, ParallelTask, SerialTask, PipelineTask } from './runners/types'; 2 | 3 | export const timeoutResult = Symbol('timeout'); 4 | 5 | export function createTimeoutResolve(timeout: number, response: T, cancelToken?: Promise): Promise { 6 | // Returning the promise Immediately when cancelToken is not provided 7 | if (typeof cancelToken === 'undefined') { 8 | return new Promise((resolve) => { 9 | setTimeout(resolve, timeout, response); 10 | }); 11 | } 12 | 13 | // clearing timeout with cancel token 14 | let timeoutId: ReturnType; 15 | const _clearTimeout = () => clearTimeout(timeoutId); 16 | cancelToken.then(_clearTimeout, _clearTimeout); 17 | 18 | const timeoutPromise = new Promise((resolve) => { 19 | timeoutId = setTimeout(resolve, timeout, response); 20 | }); 21 | 22 | // Clearing the timeout if the cancel-token promise settled first. 23 | return Promise.race([timeoutPromise, cancelToken]) as Promise; 24 | } 25 | 26 | export function createTimeoutReject(timeout: number, error: T, cancelToken?: Promise): Promise { 27 | // Returning the promise Immediately when cancelToken is not provided 28 | if (typeof cancelToken === 'undefined') { 29 | // eslint-disable-next-line promise/param-names 30 | return new Promise((_resolve, reject) => { 31 | setTimeout(reject, timeout, error); 32 | }); 33 | } 34 | 35 | // clearing timeout with cancel token 36 | let timeoutId: ReturnType; 37 | const _clearTimeout = () => clearTimeout(timeoutId); 38 | cancelToken.then(_clearTimeout, _clearTimeout); 39 | 40 | // eslint-disable-next-line promise/param-names 41 | const timeoutPromise = new Promise((_resolve, reject) => { 42 | timeoutId = setTimeout(reject, timeout, error); 43 | }); 44 | 45 | // Clearing the timeout if the cancel-token promise settled first. 46 | return Promise.race([timeoutPromise, cancelToken]) as Promise; 47 | } 48 | 49 | /* eslint-disable no-redeclare */ 50 | export function createTaskWithTimeout(task: ParallelTask, timeout: number): ParallelTask; 51 | export function createTaskWithTimeout(task: SerialTask, timeout: number): SerialTask; 52 | export function createTaskWithTimeout(task: PipelineTask, timeout: number): PipelineTask; 53 | export function createTaskWithTimeout(task: Task, timeout: number): Task { 54 | return function taskWithTimeout(arg: T) { 55 | const taskPromise = task(arg); 56 | 57 | // Use taskPromise as timeoutRejects cancelToken 58 | const timeoutPromise = createTimeoutReject(timeout, timeoutResult, taskPromise); 59 | 60 | return Promise.race([taskPromise, timeoutPromise]) as Promise; 61 | }; 62 | } 63 | /* eslint-enable no-redeclare */ 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './runners/types'; 2 | export * from './runners/index'; 3 | export * from './runners/parallel'; 4 | export * from './runners/serial'; 5 | export * from './runners/pipeline'; 6 | -------------------------------------------------------------------------------- /src/runners/index.ts: -------------------------------------------------------------------------------- 1 | import { Task, TasksRunner, TasksRunnerStatus } from './types'; 2 | 3 | export function pushTasks(taskRunner: TasksRunner, ...tasks: Task[]): number { 4 | // https://mdn.io/JavaScript/Reference/Global_Objects/Array/push 5 | return taskRunner.tasks.push(...tasks) - 1; 6 | } 7 | 8 | export function spliceTasks( 9 | taskRunner: TasksRunner, 10 | start: number, 11 | deleteCount?: number, 12 | ...tasks: Task[] 13 | ): Task[] { 14 | // https://mdn.io/JavaScript/Reference/Global_Objects/Array/splice 15 | if (typeof deleteCount === 'undefined') { 16 | return taskRunner.tasks.splice(start); 17 | } else { 18 | return taskRunner.tasks.splice(start, deleteCount, ...tasks); 19 | } 20 | } 21 | 22 | export function getTasksRunnerStatus(taskRunner: TasksRunner): TasksRunnerStatus { 23 | return taskRunner.status; 24 | } 25 | -------------------------------------------------------------------------------- /src/runners/parallel.ts: -------------------------------------------------------------------------------- 1 | import { ParallelTask, ParallelTasksRunner, RunParallelTasksResult } from './types'; 2 | 3 | export function createParallelTasksRunner(...tasks: ParallelTask[]): ParallelTasksRunner { 4 | return Object.create(null, { 5 | tasks: { 6 | value: tasks, 7 | }, 8 | pendingTasks: { 9 | value: new WeakMap(), 10 | }, 11 | executeCount: { 12 | value: 0, 13 | writable: true, 14 | }, 15 | status: { 16 | value: 'standby', 17 | writable: true, 18 | }, 19 | }); 20 | } 21 | 22 | export function runParallelTasks(taskRunner: ParallelTasksRunner): RunParallelTasksResult { 23 | taskRunner.status = 'pending'; 24 | const currentExecuteCount = ++taskRunner.executeCount; 25 | 26 | const executedTasks = taskRunner.tasks.map((task) => { 27 | // Just for new tasks added to the runner 28 | if (!taskRunner.pendingTasks.has(task)) { 29 | taskRunner.pendingTasks.set(task, task()); 30 | } 31 | 32 | return taskRunner.pendingTasks.get(task) as Promise; 33 | }); 34 | 35 | return Promise.allSettled(executedTasks).then((results) => { 36 | // If this is the result of the last run, then set the state 37 | if (currentExecuteCount === taskRunner.executeCount) { 38 | taskRunner.status = 'fulfilled'; 39 | } 40 | 41 | return results; 42 | }); 43 | } 44 | 45 | export function getParallelTasks(taskRunner: ParallelTasksRunner, index: number): Promise { 46 | if (taskRunner.status === 'standby') { 47 | return Promise.reject(new Error('Task runner not yet started')); 48 | } 49 | 50 | if (index < 0 || index >= taskRunner.tasks.length) { 51 | return Promise.reject(new Error('Index out of bounds')); 52 | } 53 | 54 | const task = taskRunner.tasks[index]; 55 | if (!taskRunner.pendingTasks.has(task)) { 56 | return Promise.reject(new Error('Run the runner again after modifications')); 57 | } 58 | 59 | return taskRunner.pendingTasks.get(task) as Promise; 60 | } 61 | -------------------------------------------------------------------------------- /src/runners/pipeline.ts: -------------------------------------------------------------------------------- 1 | import { PipelineTask, PipelineTasksRunner, RunPipelineTasksResult } from './types'; 2 | 3 | export function createPipelineTasksRunner(...tasks: PipelineTask[]): PipelineTasksRunner { 4 | return Object.create(null, { 5 | tasks: { 6 | value: tasks, 7 | }, 8 | pendingTasks: { 9 | value: new WeakMap(), 10 | }, 11 | executeCount: { 12 | value: 0, 13 | writable: true, 14 | }, 15 | status: { 16 | value: 'standby', 17 | writable: true, 18 | }, 19 | runnerFirstArgCache: { 20 | value: undefined, 21 | writable: true, 22 | }, 23 | }); 24 | } 25 | 26 | export async function runPipelineTasks(taskRunner: PipelineTasksRunner, firstArg: T): RunPipelineTasksResult { 27 | taskRunner.status = 'pending'; 28 | const currentExecuteCount = ++taskRunner.executeCount; 29 | 30 | // The difference in the firstArg cause to start the runner from the beginning 31 | let forceReRun = false; 32 | if (taskRunner.runnerFirstArgCache !== firstArg) { 33 | taskRunner.runnerFirstArgCache = firstArg; 34 | forceReRun = true; 35 | } 36 | 37 | // Iterate the tasks with serial-iterator 38 | let lastResult: T = firstArg; 39 | const taskIterator = iterateTasks(taskRunner, lastResult, forceReRun); 40 | 41 | let nextTask = taskIterator.next(); 42 | while (!nextTask.done) { 43 | try { 44 | // run tasks one by one 45 | lastResult = await nextTask.value; 46 | } catch (error) { 47 | // If this is the result of the last run, then set the state 48 | if (currentExecuteCount === taskRunner.executeCount) { 49 | taskRunner.status = 'rejected'; 50 | } 51 | 52 | // If task failed, the runner stops pending tasks 53 | return Promise.reject(error); 54 | } 55 | 56 | nextTask = taskIterator.next(lastResult); 57 | } 58 | 59 | // If this is the result of the last run, then set the state 60 | if (currentExecuteCount === taskRunner.executeCount) { 61 | taskRunner.status = 'fulfilled'; 62 | } 63 | return Promise.resolve(lastResult); 64 | } 65 | 66 | function* iterateTasks(taskRunner: PipelineTasksRunner, firstArg: T, forceReRun = false): Generator> { 67 | // lastResult need as next task argument 68 | let lastResult: T = firstArg; 69 | 70 | // Creating a clone from tasks to get every run separate 71 | // and keep a reference for each task until the end because of WeakMap 72 | const tasksClone = [...taskRunner.tasks]; 73 | for (let i = 0; i < tasksClone.length; i++) { 74 | // start pending the task if it's not already pending 75 | if (!taskRunner.pendingTasks.has(tasksClone[i]) || forceReRun) { 76 | forceReRun = true; 77 | taskRunner.pendingTasks.set(tasksClone[i], tasksClone[i](lastResult)); 78 | } 79 | 80 | lastResult = (yield taskRunner.pendingTasks.get(tasksClone[i]) as Promise) as unknown as T; 81 | } 82 | } 83 | 84 | export async function getPipelineTasks(taskRunner: PipelineTasksRunner, index: number): Promise { 85 | if (taskRunner.status === 'standby') { 86 | return Promise.reject(new Error('Task runner is not yet started')); 87 | } 88 | 89 | // show error if the index is out of bounds 90 | if (index < 0 || index >= taskRunner.tasks.length) { 91 | return Promise.reject(new Error('Index out of bounds')); 92 | } 93 | 94 | // Task already executed 95 | const task = taskRunner.tasks[index]; 96 | if (taskRunner.pendingTasks.has(task)) { 97 | return taskRunner.pendingTasks.get(task) as Promise; 98 | } 99 | 100 | // At this point, the task is not executed yet. 101 | // Running the tasks one by one until the index is reached. 102 | let lastResult: T = taskRunner.runnerFirstArgCache as T; 103 | const taskIterator = iterateTasks(taskRunner, lastResult, false); 104 | for (let i = 0; i < index; i++) { 105 | try { 106 | const nextTask = taskIterator.next(lastResult); 107 | lastResult = await nextTask.value; 108 | } catch { 109 | return Promise.reject(new Error('Task failed before reach')); 110 | } 111 | } 112 | 113 | return taskIterator.next().value; 114 | } 115 | -------------------------------------------------------------------------------- /src/runners/serial.ts: -------------------------------------------------------------------------------- 1 | import { SerialTask, SerialTasksRunner, RunSerialTasksResult } from './types'; 2 | 3 | export function createSerialTasksRunner(...tasks: SerialTask[]): SerialTasksRunner { 4 | return Object.create(null, { 5 | tasks: { 6 | value: tasks, 7 | }, 8 | pendingTasks: { 9 | value: new WeakMap(), 10 | }, 11 | executeCount: { 12 | value: 0, 13 | writable: true, 14 | }, 15 | status: { 16 | value: 'standby', 17 | writable: true, 18 | }, 19 | }); 20 | } 21 | 22 | export async function runSerialTasks(taskRunner: SerialTasksRunner): RunSerialTasksResult { 23 | taskRunner.status = 'pending'; 24 | const currentExecuteCount = ++taskRunner.executeCount; 25 | const results: PromiseSettledResult[] = []; 26 | const taskIterator = iterateTasks(taskRunner); 27 | 28 | // Iterate the tasks with serial-iterator 29 | let nextTask = taskIterator.next(); 30 | while (!nextTask.done) { 31 | try { 32 | // run tasks one by one 33 | const result = await nextTask.value; 34 | results.push({ 35 | status: 'fulfilled', 36 | value: result, 37 | }); 38 | } catch (error) { 39 | // If this is the result of the last run, then set the state 40 | if (currentExecuteCount === taskRunner.executeCount) { 41 | taskRunner.status = 'rejected'; 42 | } 43 | 44 | // If task failed, the runner stops pending tasks 45 | results.push({ 46 | status: 'rejected', 47 | reason: error, 48 | }); 49 | return Promise.reject(results); 50 | } 51 | 52 | // If this is the result of the last run, then set the state 53 | if (currentExecuteCount === taskRunner.executeCount) { 54 | taskRunner.status = 'fulfilled'; 55 | } 56 | nextTask = taskIterator.next(); 57 | } 58 | 59 | // If this is the result of the last run, then set the state 60 | if (currentExecuteCount === taskRunner.executeCount) { 61 | taskRunner.status = 'fulfilled'; 62 | } 63 | return Promise.resolve(results); 64 | } 65 | 66 | function* iterateTasks(taskRunner: SerialTasksRunner): Generator> { 67 | // Creating a clone from tasks to get every run separate 68 | // and keep a reference for each task until the end because of WeakMap 69 | const tasksClone = [...taskRunner.tasks]; 70 | for (let i = 0; i < tasksClone.length; i++) { 71 | if (!taskRunner.pendingTasks.has(tasksClone[i])) { 72 | taskRunner.pendingTasks.set(tasksClone[i], tasksClone[i]()); 73 | } 74 | 75 | yield taskRunner.pendingTasks.get(tasksClone[i]) as Promise; 76 | } 77 | } 78 | 79 | export async function getSerialTasks(taskRunner: SerialTasksRunner, index: number): Promise { 80 | if (taskRunner.status === 'standby') { 81 | return Promise.reject(new Error('Task runner is not yet started')); 82 | } 83 | 84 | if (index < 0 || index >= taskRunner.tasks.length) { 85 | return Promise.reject(new Error('Index out of bounds')); 86 | } 87 | 88 | // Task already executed 89 | const task = taskRunner.tasks[index]; 90 | if (taskRunner.pendingTasks.has(task)) { 91 | return taskRunner.pendingTasks.get(task) as Promise; 92 | } 93 | 94 | // At this point, the task is not executed yet. 95 | // Running the tasks one by one until the index is reached. 96 | const taskIterator = iterateTasks(taskRunner); 97 | for (let i = 0; i < index; i++) { 98 | try { 99 | const nextTask = taskIterator.next(); 100 | await nextTask.value; 101 | } catch { 102 | return Promise.reject(new Error('Task failed before reach')); 103 | } 104 | } 105 | 106 | return taskIterator.next().value; 107 | } 108 | -------------------------------------------------------------------------------- /src/runners/types.ts: -------------------------------------------------------------------------------- 1 | export type TasksRunnerStatus = 'standby' | 'pending' | 'fulfilled' | 'rejected'; 2 | 3 | export type ParallelTask = () => Promise; 4 | export type SerialTask = () => Promise; 5 | export type PipelineTask = (perviousResult: T) => Promise; 6 | export type Task = ParallelTask | SerialTask | PipelineTask; 7 | 8 | export interface TasksRunner { 9 | tasks: Task[]; 10 | pendingTasks: WeakMap, Promise>; 11 | executeCount: number; 12 | status: TasksRunnerStatus; 13 | } 14 | 15 | export interface ParallelTasksRunner extends TasksRunner { 16 | tasks: ParallelTask[]; 17 | } 18 | export interface SerialTasksRunner extends TasksRunner { 19 | tasks: SerialTask[]; 20 | } 21 | export interface PipelineTasksRunner extends TasksRunner { 22 | tasks: PipelineTask[]; 23 | runnerFirstArgCache: T | undefined; 24 | } 25 | 26 | export type RunParallelTasksResult = Promise[]>; 27 | export type RunSerialTasksResult = Promise[]>; 28 | export type RunPipelineTasksResult = Promise; 29 | -------------------------------------------------------------------------------- /test/base.test.mjs: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { createTimeoutResolve } from '../dist/helpers.mjs'; 4 | import { createParallelTasksRunner, pushTasks, spliceTasks } from '../dist/index.mjs'; 5 | 6 | use(chaiAsPromised); 7 | 8 | // We use ParallelTasksRunner to test the basic functionality of the library. 9 | 10 | describe('Basics', () => { 11 | it('pushing one task to the tasks-runner', () => { 12 | const runner = createParallelTasksRunner(); 13 | 14 | const firstTask = () => createTimeoutResolve(1, 1); 15 | expect(pushTasks(runner, firstTask)).to.equal(0); 16 | }); 17 | 18 | it('pushing three tasks at the same time to the tasks-runner', () => { 19 | const runner = createParallelTasksRunner(); 20 | 21 | const firstTask = () => createTimeoutResolve(1, 1); 22 | const secondTask = () => createTimeoutResolve(2, 2); 23 | const thirdTask = () => createTimeoutResolve(3, 3); 24 | expect(pushTasks(runner, firstTask, secondTask, thirdTask)).to.equal(2); 25 | }); 26 | 27 | it('pushing empty list to the tasks-runner', () => { 28 | const runner = createParallelTasksRunner(); 29 | 30 | expect(pushTasks(runner)).to.equal(-1); 31 | }); 32 | 33 | it('pushing tasks in a more complicated way', () => { 34 | const runner = createParallelTasksRunner(); 35 | 36 | // pushing the first task 37 | const firstTask = () => createTimeoutResolve(1, 1); 38 | expect(pushTasks(runner, firstTask)).to.equal(0); 39 | 40 | // pushing the second and third tasks at the same time 41 | const secondTask = () => createTimeoutResolve(2, 2); 42 | const thirdTask = () => createTimeoutResolve(3, 3); 43 | expect(pushTasks(runner, secondTask, thirdTask)).to.equal(2); 44 | 45 | // add forth task in separate call 46 | const forthTask = () => createTimeoutResolve(4, 4); 47 | expect(pushTasks(runner, forthTask)).to.equal(3); 48 | 49 | // pushing noting must return the last item index 50 | expect(pushTasks(runner)).to.equal(3); 51 | }); 52 | 53 | it('splicing tasks with just start parameter', () => { 54 | const runner = createParallelTasksRunner(); 55 | 56 | // create tasks 57 | const count = 5; 58 | const tasks = []; 59 | for (let i = 0; i < count; i++) { 60 | tasks.push(() => createTimeoutResolve(i, i)); 61 | } 62 | 63 | // push tasks 64 | pushTasks(runner, ...tasks); 65 | 66 | // splice tasks 67 | expect(spliceTasks(runner, 2)).to.have.length(count - 2); 68 | 69 | // get tasks length 70 | // pushing noting must return the last item index 71 | expect(pushTasks(runner) + 1).to.equal(2); 72 | }); 73 | 74 | it('splicing tasks with start and deleteCount parameter', () => { 75 | const runner = createParallelTasksRunner(); 76 | 77 | // create tasks 78 | const count = 5; 79 | const tasks = []; 80 | for (let i = 0; i < count; i++) { 81 | tasks.push(() => createTimeoutResolve(i, i)); 82 | } 83 | 84 | // push tasks 85 | pushTasks(runner, ...tasks); 86 | 87 | // splice tasks 88 | expect(spliceTasks(runner, 2, 2)).to.have.length(2); 89 | 90 | // get tasks length 91 | // pushing noting must return the last item index 92 | expect(pushTasks(runner) + 1).to.equal(count - 2); 93 | }); 94 | 95 | it('splicing tasks with start and deleteCount and newTasks parameter', () => { 96 | const runner = createParallelTasksRunner(); 97 | 98 | // create tasks 99 | const count = 5; 100 | const tasks = []; 101 | for (let i = 0; i < count; i++) { 102 | tasks.push(() => createTimeoutResolve(i, i)); 103 | } 104 | 105 | // create new tasks 106 | const newCount = 4; 107 | const newTasks = []; 108 | for (let i = 0; i < newCount; i++) { 109 | newTasks.push(() => createTimeoutResolve(i, i)); 110 | } 111 | 112 | // push tasks 113 | pushTasks(runner, ...tasks); 114 | 115 | // splice tasks 116 | expect(spliceTasks(runner, 2, 2, ...newTasks)).to.have.length(2); 117 | 118 | // get tasks length 119 | // pushing noting must return the last item index 120 | expect(pushTasks(runner) + 1).to.equal(count - 2 + newCount); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/helpers.test.mjs: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { createTimeoutResolve, createTimeoutReject, createTaskWithTimeout, timeoutResult } from '../dist/helpers.mjs'; 4 | 5 | use(chaiAsPromised); 6 | 7 | describe('Helpers', () => { 8 | it('create timeout resolve', async () => { 9 | await expect(createTimeoutResolve(2, 'resolve')).to.eventually.equal('resolve'); 10 | }); 11 | 12 | it('create timeout reject', async () => { 13 | await expect(createTimeoutReject(2, 'reject')).to.eventually.rejectedWith('reject'); 14 | }); 15 | 16 | it('create task with timeout: fulfilled task', async () => { 17 | const task = createTaskWithTimeout(createTimeoutResolve.bind(null, 2, 'resolve'), 3); 18 | await expect(task()).to.eventually.equal('resolve'); 19 | }); 20 | 21 | it('create task with timeout: rejected task', async () => { 22 | const task = createTaskWithTimeout(createTimeoutReject.bind(null, 2, 'reject'), 3); 23 | await expect(task()).to.eventually.rejectedWith('reject'); 24 | }); 25 | 26 | it('create task with timeout: rejected timeout', async () => { 27 | const task = createTaskWithTimeout(createTimeoutReject.bind(null, 4, 'reject'), 3); 28 | await expect(task()).to.eventually.rejectedWith(timeoutResult); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/parallel.test.mjs: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { createTimeoutResolve, createTimeoutReject } from '../dist/helpers.mjs'; 4 | import { 5 | createParallelTasksRunner, 6 | runParallelTasks, 7 | getParallelTasks, 8 | pushTasks, 9 | spliceTasks, 10 | } from '../dist/index.mjs'; 11 | 12 | use(chaiAsPromised); 13 | 14 | describe('ParallelTasksRunner', () => { 15 | it('Running some fulfilled tasks in parallel', async () => { 16 | const runner = createParallelTasksRunner(); 17 | 18 | // pushing 3 tasks 19 | const firstTask = () => createTimeoutResolve(1, 1); 20 | const secondTask = () => createTimeoutResolve(2, 2); 21 | const thirdTask = () => createTimeoutResolve(3, 3); 22 | pushTasks(runner, firstTask, secondTask, thirdTask); 23 | 24 | expect(runner.status).to.equal('standby'); 25 | 26 | const runningTasks = runParallelTasks(runner); 27 | 28 | expect(runner.status).to.equal('pending'); 29 | 30 | const results = await runningTasks; 31 | 32 | expect(runner.status).to.equal('fulfilled'); 33 | 34 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 35 | }); 36 | 37 | it('Running some fulfilled & rejected tasks in parallel', async () => { 38 | const runner = createParallelTasksRunner(); 39 | 40 | // pushing 3 tasks 41 | const firstTask = () => createTimeoutResolve(1, 1); 42 | const secondTask = () => createTimeoutReject(2, 2); 43 | const thirdTask = () => createTimeoutResolve(3, 3); 44 | pushTasks(runner, firstTask, secondTask, thirdTask); 45 | 46 | expect(runner.status).to.equal('standby'); 47 | 48 | const runningTasks = runParallelTasks(runner); 49 | 50 | expect(runner.status).to.equal('pending'); 51 | 52 | const results = await runningTasks; 53 | 54 | expect(runner.status).to.equal('fulfilled'); 55 | 56 | expect(results).to.deep.equal([ 57 | { status: 'fulfilled', value: 1 }, 58 | { status: 'rejected', reason: 2 }, 59 | { status: 'fulfilled', value: 3 }, 60 | ]); 61 | }); 62 | 63 | it('Running some rejected tasks in parallel', async () => { 64 | const runner = createParallelTasksRunner(); 65 | 66 | // pushing 3 tasks 67 | const firstTask = () => createTimeoutReject(1, 1); 68 | const secondTask = () => createTimeoutReject(2, 2); 69 | const thirdTask = () => createTimeoutReject(3, 3); 70 | pushTasks(runner, firstTask, secondTask, thirdTask); 71 | 72 | expect(runner.status).to.equal('standby'); 73 | 74 | const runningTasks = runParallelTasks(runner); 75 | 76 | expect(runner.status).to.equal('pending'); 77 | 78 | const results = await runningTasks; 79 | 80 | expect(runner.status).to.equal('fulfilled'); 81 | 82 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'rejected', reason: value }))); 83 | }); 84 | 85 | it('Adding some tasks after first run (without waiting) & run again', async () => { 86 | const runner = createParallelTasksRunner(); 87 | 88 | // pushing 3 tasks 89 | const firstTask = () => createTimeoutResolve(1, 1); 90 | const secondTask = () => createTimeoutResolve(2, 2); 91 | const thirdTask = () => createTimeoutResolve(3, 3); 92 | pushTasks(runner, firstTask, secondTask, thirdTask); 93 | 94 | expect(runner.status).to.equal('standby'); 95 | 96 | const firstRunningTasks = runParallelTasks(runner); 97 | 98 | expect(runner.status).to.equal('pending'); 99 | 100 | // splice second task and insert two new tasks 101 | spliceTasks( 102 | runner, 103 | 1, 104 | 1, 105 | () => createTimeoutResolve(4, 4), 106 | () => createTimeoutResolve(5, 5), 107 | ); 108 | 109 | const secondRunningTasks = runParallelTasks(runner); 110 | 111 | expect(runner.status).to.equal('pending'); 112 | 113 | const firstResults = await firstRunningTasks; 114 | 115 | expect(firstResults).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 116 | 117 | // status should be still pending because it depend to last run status 118 | expect(runner.status).to.equal('pending'); 119 | 120 | const secondResults = await secondRunningTasks; 121 | 122 | expect(runner.status).to.equal('fulfilled'); 123 | 124 | expect(secondResults).to.deep.equal([1, 4, 5, 3].map((value) => ({ status: 'fulfilled', value }))); 125 | }); 126 | 127 | it('Getting a specific task before running', async () => { 128 | const runner = createParallelTasksRunner(); 129 | 130 | // pushing 3 tasks 131 | const firstTask = () => createTimeoutResolve(1, 1); 132 | const secondTask = () => createTimeoutResolve(2, 2); 133 | const thirdTask = () => createTimeoutResolve(3, 3); 134 | pushTasks(runner, firstTask, secondTask, thirdTask); 135 | 136 | await expect(getParallelTasks(runner, 2)).to.eventually.rejectedWith(Error); 137 | }); 138 | 139 | it('Getting a specific task after running but out of the bound', async () => { 140 | const runner = createParallelTasksRunner(); 141 | 142 | // pushing 3 tasks 143 | const firstTask = () => createTimeoutResolve(1, 1); 144 | const secondTask = () => createTimeoutResolve(2, 2); 145 | const thirdTask = () => createTimeoutResolve(3, 3); 146 | pushTasks(runner, firstTask, secondTask, thirdTask); 147 | 148 | await expect(getParallelTasks(runner, 4)).to.eventually.rejectedWith(Error); 149 | }); 150 | 151 | it('Getting a specific task after running', async () => { 152 | const runner = createParallelTasksRunner(); 153 | 154 | // pushing 3 tasks 155 | const firstTask = () => createTimeoutResolve(1, 1); 156 | const secondTask = () => createTimeoutResolve(2, 2); 157 | const thirdTask = () => createTimeoutResolve(3, 3); 158 | pushTasks(runner, firstTask, secondTask, thirdTask); 159 | 160 | runParallelTasks(runner); 161 | 162 | await expect(getParallelTasks(runner, 1)).to.eventually.equal(2); 163 | }); 164 | 165 | it('Getting a specific task that added after the first run but before the second run', async () => { 166 | const runner = createParallelTasksRunner(); 167 | 168 | // pushing 3 tasks 169 | const firstTask = () => createTimeoutResolve(1, 1); 170 | const secondTask = () => createTimeoutResolve(2, 2); 171 | const thirdTask = () => createTimeoutResolve(3, 3); 172 | pushTasks(runner, firstTask, secondTask, thirdTask); 173 | 174 | runParallelTasks(runner); 175 | 176 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 177 | 178 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 179 | }); 180 | 181 | it('Getting a specific task that added after the first and the second run', async () => { 182 | const runner = createParallelTasksRunner(); 183 | 184 | // pushing 3 tasks 185 | const firstTask = () => createTimeoutResolve(1, 1); 186 | const secondTask = () => createTimeoutResolve(2, 2); 187 | const thirdTask = () => createTimeoutResolve(3, 3); 188 | pushTasks(runner, firstTask, secondTask, thirdTask); 189 | 190 | runParallelTasks(runner); 191 | 192 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 193 | 194 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /test/pipeline.test.mjs: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { createTimeoutResolve, createTimeoutReject } from '../dist/helpers.mjs'; 4 | import { 5 | createPipelineTasksRunner, 6 | runParallelTasks, 7 | getParallelTasks, 8 | pushTasks, 9 | spliceTasks, 10 | } from '../dist/index.mjs'; 11 | 12 | use(chaiAsPromised); 13 | 14 | describe('PipelineTasksRunner', () => { 15 | it('Running some fulfilled tasks in parallel', async () => { 16 | const runner = createPipelineTasksRunner(); 17 | 18 | // pushing 3 tasks 19 | const firstTask = () => createTimeoutResolve(1, 1); 20 | const secondTask = () => createTimeoutResolve(2, 2); 21 | const thirdTask = () => createTimeoutResolve(3, 3); 22 | pushTasks(runner, firstTask, secondTask, thirdTask); 23 | 24 | expect(runner.status).to.equal('standby'); 25 | 26 | const runningTasks = runParallelTasks(runner); 27 | 28 | expect(runner.status).to.equal('pending'); 29 | 30 | const results = await runningTasks; 31 | 32 | expect(runner.status).to.equal('fulfilled'); 33 | 34 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 35 | }); 36 | 37 | it('Running some fulfilled & rejected tasks in parallel', async () => { 38 | const runner = createPipelineTasksRunner(); 39 | 40 | // pushing 3 tasks 41 | const firstTask = () => createTimeoutResolve(1, 1); 42 | const secondTask = () => createTimeoutReject(2, 2); 43 | const thirdTask = () => createTimeoutResolve(3, 3); 44 | pushTasks(runner, firstTask, secondTask, thirdTask); 45 | 46 | expect(runner.status).to.equal('standby'); 47 | 48 | const runningTasks = runParallelTasks(runner); 49 | 50 | expect(runner.status).to.equal('pending'); 51 | 52 | const results = await runningTasks; 53 | 54 | expect(runner.status).to.equal('fulfilled'); 55 | 56 | expect(results).to.deep.equal([ 57 | { status: 'fulfilled', value: 1 }, 58 | { status: 'rejected', reason: 2 }, 59 | { status: 'fulfilled', value: 3 }, 60 | ]); 61 | }); 62 | 63 | it('Running some rejected tasks in parallel', async () => { 64 | const runner = createPipelineTasksRunner(); 65 | 66 | // pushing 3 tasks 67 | const firstTask = () => createTimeoutReject(1, 1); 68 | const secondTask = () => createTimeoutReject(2, 2); 69 | const thirdTask = () => createTimeoutReject(3, 3); 70 | pushTasks(runner, firstTask, secondTask, thirdTask); 71 | 72 | expect(runner.status).to.equal('standby'); 73 | 74 | const runningTasks = runParallelTasks(runner); 75 | 76 | expect(runner.status).to.equal('pending'); 77 | 78 | const results = await runningTasks; 79 | 80 | expect(runner.status).to.equal('fulfilled'); 81 | 82 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'rejected', reason: value }))); 83 | }); 84 | 85 | it('Adding some tasks after first run (without waiting) & run again', async () => { 86 | const runner = createPipelineTasksRunner(); 87 | 88 | // pushing 3 tasks 89 | const firstTask = () => createTimeoutResolve(1, 1); 90 | const secondTask = () => createTimeoutResolve(2, 2); 91 | const thirdTask = () => createTimeoutResolve(3, 3); 92 | pushTasks(runner, firstTask, secondTask, thirdTask); 93 | 94 | expect(runner.status).to.equal('standby'); 95 | 96 | const firstRunningTasks = runParallelTasks(runner); 97 | 98 | expect(runner.status).to.equal('pending'); 99 | 100 | // splice second task and insert two new tasks 101 | spliceTasks( 102 | runner, 103 | 1, 104 | 1, 105 | () => createTimeoutResolve(4, 4), 106 | () => createTimeoutResolve(5, 5), 107 | ); 108 | 109 | const secondRunningTasks = runParallelTasks(runner); 110 | 111 | expect(runner.status).to.equal('pending'); 112 | 113 | const firstResults = await firstRunningTasks; 114 | 115 | expect(firstResults).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 116 | 117 | // status should be still pending because it depend to last run status 118 | expect(runner.status).to.equal('pending'); 119 | 120 | const secondResults = await secondRunningTasks; 121 | 122 | expect(runner.status).to.equal('fulfilled'); 123 | 124 | expect(secondResults).to.deep.equal([1, 4, 5, 3].map((value) => ({ status: 'fulfilled', value }))); 125 | }); 126 | 127 | it('Getting a specific task before running', async () => { 128 | const runner = createPipelineTasksRunner(); 129 | 130 | // pushing 3 tasks 131 | const firstTask = () => createTimeoutResolve(1, 1); 132 | const secondTask = () => createTimeoutResolve(2, 2); 133 | const thirdTask = () => createTimeoutResolve(3, 3); 134 | pushTasks(runner, firstTask, secondTask, thirdTask); 135 | 136 | await expect(getParallelTasks(runner, 2)).to.eventually.rejectedWith(Error); 137 | }); 138 | 139 | it('Getting a specific task after running but out of the bound', async () => { 140 | const runner = createPipelineTasksRunner(); 141 | 142 | // pushing 3 tasks 143 | const firstTask = () => createTimeoutResolve(1, 1); 144 | const secondTask = () => createTimeoutResolve(2, 2); 145 | const thirdTask = () => createTimeoutResolve(3, 3); 146 | pushTasks(runner, firstTask, secondTask, thirdTask); 147 | 148 | await expect(getParallelTasks(runner, 4)).to.eventually.rejectedWith(Error); 149 | }); 150 | 151 | it('Getting a specific task after running', async () => { 152 | const runner = createPipelineTasksRunner(); 153 | 154 | // pushing 3 tasks 155 | const firstTask = () => createTimeoutResolve(1, 1); 156 | const secondTask = () => createTimeoutResolve(2, 2); 157 | const thirdTask = () => createTimeoutResolve(3, 3); 158 | pushTasks(runner, firstTask, secondTask, thirdTask); 159 | 160 | runParallelTasks(runner); 161 | 162 | await expect(getParallelTasks(runner, 1)).to.eventually.equal(2); 163 | }); 164 | 165 | it('Getting a specific task that added after the first run but before the second run', async () => { 166 | const runner = createPipelineTasksRunner(); 167 | 168 | // pushing 3 tasks 169 | const firstTask = () => createTimeoutResolve(1, 1); 170 | const secondTask = () => createTimeoutResolve(2, 2); 171 | const thirdTask = () => createTimeoutResolve(3, 3); 172 | pushTasks(runner, firstTask, secondTask, thirdTask); 173 | 174 | runParallelTasks(runner); 175 | 176 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 177 | 178 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 179 | }); 180 | 181 | it('Getting a specific task that added after the first and the second run', async () => { 182 | const runner = createPipelineTasksRunner(); 183 | 184 | // pushing 3 tasks 185 | const firstTask = () => createTimeoutResolve(1, 1); 186 | const secondTask = () => createTimeoutResolve(2, 2); 187 | const thirdTask = () => createTimeoutResolve(3, 3); 188 | pushTasks(runner, firstTask, secondTask, thirdTask); 189 | 190 | runParallelTasks(runner); 191 | 192 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 193 | 194 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /test/serial.test.mjs: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { createTimeoutResolve, createTimeoutReject } from '../dist/helpers.mjs'; 4 | import { createSerialTasksRunner, runParallelTasks, getParallelTasks, pushTasks, spliceTasks } from '../dist/index.mjs'; 5 | 6 | use(chaiAsPromised); 7 | 8 | describe('SerialTasksRunner', () => { 9 | it('Running some fulfilled tasks in parallel', async () => { 10 | const runner = createSerialTasksRunner(); 11 | 12 | // pushing 3 tasks 13 | const firstTask = () => createTimeoutResolve(1, 1); 14 | const secondTask = () => createTimeoutResolve(2, 2); 15 | const thirdTask = () => createTimeoutResolve(3, 3); 16 | pushTasks(runner, firstTask, secondTask, thirdTask); 17 | 18 | expect(runner.status).to.equal('standby'); 19 | 20 | const runningTasks = runParallelTasks(runner); 21 | 22 | expect(runner.status).to.equal('pending'); 23 | 24 | const results = await runningTasks; 25 | 26 | expect(runner.status).to.equal('fulfilled'); 27 | 28 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 29 | }); 30 | 31 | it('Running some fulfilled & rejected tasks in parallel', async () => { 32 | const runner = createSerialTasksRunner(); 33 | 34 | // pushing 3 tasks 35 | const firstTask = () => createTimeoutResolve(1, 1); 36 | const secondTask = () => createTimeoutReject(2, 2); 37 | const thirdTask = () => createTimeoutResolve(3, 3); 38 | pushTasks(runner, firstTask, secondTask, thirdTask); 39 | 40 | expect(runner.status).to.equal('standby'); 41 | 42 | const runningTasks = runParallelTasks(runner); 43 | 44 | expect(runner.status).to.equal('pending'); 45 | 46 | const results = await runningTasks; 47 | 48 | expect(runner.status).to.equal('fulfilled'); 49 | 50 | expect(results).to.deep.equal([ 51 | { status: 'fulfilled', value: 1 }, 52 | { status: 'rejected', reason: 2 }, 53 | { status: 'fulfilled', value: 3 }, 54 | ]); 55 | }); 56 | 57 | it('Running some rejected tasks in parallel', async () => { 58 | const runner = createSerialTasksRunner(); 59 | 60 | // pushing 3 tasks 61 | const firstTask = () => createTimeoutReject(1, 1); 62 | const secondTask = () => createTimeoutReject(2, 2); 63 | const thirdTask = () => createTimeoutReject(3, 3); 64 | pushTasks(runner, firstTask, secondTask, thirdTask); 65 | 66 | expect(runner.status).to.equal('standby'); 67 | 68 | const runningTasks = runParallelTasks(runner); 69 | 70 | expect(runner.status).to.equal('pending'); 71 | 72 | const results = await runningTasks; 73 | 74 | expect(runner.status).to.equal('fulfilled'); 75 | 76 | expect(results).to.deep.equal([1, 2, 3].map((value) => ({ status: 'rejected', reason: value }))); 77 | }); 78 | 79 | it('Adding some tasks after first run (without waiting) & run again', async () => { 80 | const runner = createSerialTasksRunner(); 81 | 82 | // pushing 3 tasks 83 | const firstTask = () => createTimeoutResolve(1, 1); 84 | const secondTask = () => createTimeoutResolve(2, 2); 85 | const thirdTask = () => createTimeoutResolve(3, 3); 86 | pushTasks(runner, firstTask, secondTask, thirdTask); 87 | 88 | expect(runner.status).to.equal('standby'); 89 | 90 | const firstRunningTasks = runParallelTasks(runner); 91 | 92 | expect(runner.status).to.equal('pending'); 93 | 94 | // splice second task and insert two new tasks 95 | spliceTasks( 96 | runner, 97 | 1, 98 | 1, 99 | () => createTimeoutResolve(4, 4), 100 | () => createTimeoutResolve(5, 5), 101 | ); 102 | 103 | const secondRunningTasks = runParallelTasks(runner); 104 | 105 | expect(runner.status).to.equal('pending'); 106 | 107 | const firstResults = await firstRunningTasks; 108 | 109 | expect(firstResults).to.deep.equal([1, 2, 3].map((value) => ({ status: 'fulfilled', value }))); 110 | 111 | // status should be still pending because it depend to last run status 112 | expect(runner.status).to.equal('pending'); 113 | 114 | const secondResults = await secondRunningTasks; 115 | 116 | expect(runner.status).to.equal('fulfilled'); 117 | 118 | expect(secondResults).to.deep.equal([1, 4, 5, 3].map((value) => ({ status: 'fulfilled', value }))); 119 | }); 120 | 121 | it('Getting a specific task before running', async () => { 122 | const runner = createSerialTasksRunner(); 123 | 124 | // pushing 3 tasks 125 | const firstTask = () => createTimeoutResolve(1, 1); 126 | const secondTask = () => createTimeoutResolve(2, 2); 127 | const thirdTask = () => createTimeoutResolve(3, 3); 128 | pushTasks(runner, firstTask, secondTask, thirdTask); 129 | 130 | await expect(getParallelTasks(runner, 2)).to.eventually.rejectedWith(Error); 131 | }); 132 | 133 | it('Getting a specific task after running but out of the bound', async () => { 134 | const runner = createSerialTasksRunner(); 135 | 136 | // pushing 3 tasks 137 | const firstTask = () => createTimeoutResolve(1, 1); 138 | const secondTask = () => createTimeoutResolve(2, 2); 139 | const thirdTask = () => createTimeoutResolve(3, 3); 140 | pushTasks(runner, firstTask, secondTask, thirdTask); 141 | 142 | await expect(getParallelTasks(runner, 4)).to.eventually.rejectedWith(Error); 143 | }); 144 | 145 | it('Getting a specific task after running', async () => { 146 | const runner = createSerialTasksRunner(); 147 | 148 | // pushing 3 tasks 149 | const firstTask = () => createTimeoutResolve(1, 1); 150 | const secondTask = () => createTimeoutResolve(2, 2); 151 | const thirdTask = () => createTimeoutResolve(3, 3); 152 | pushTasks(runner, firstTask, secondTask, thirdTask); 153 | 154 | runParallelTasks(runner); 155 | 156 | await expect(getParallelTasks(runner, 1)).to.eventually.equal(2); 157 | }); 158 | 159 | it('Getting a specific task that added after the first run but before the second run', async () => { 160 | const runner = createSerialTasksRunner(); 161 | 162 | // pushing 3 tasks 163 | const firstTask = () => createTimeoutResolve(1, 1); 164 | const secondTask = () => createTimeoutResolve(2, 2); 165 | const thirdTask = () => createTimeoutResolve(3, 3); 166 | pushTasks(runner, firstTask, secondTask, thirdTask); 167 | 168 | runParallelTasks(runner); 169 | 170 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 171 | 172 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 173 | }); 174 | 175 | it('Getting a specific task that added after the first and the second run', async () => { 176 | const runner = createSerialTasksRunner(); 177 | 178 | // pushing 3 tasks 179 | const firstTask = () => createTimeoutResolve(1, 1); 180 | const secondTask = () => createTimeoutResolve(2, 2); 181 | const thirdTask = () => createTimeoutResolve(3, 3); 182 | pushTasks(runner, firstTask, secondTask, thirdTask); 183 | 184 | runParallelTasks(runner); 185 | 186 | spliceTasks(runner, 1, 1, () => createTimeoutResolve(4, 4)); 187 | 188 | await expect(getParallelTasks(runner, 1)).to.eventually.rejectedWith(Error); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "strict": true, 9 | "declaration": true, 10 | "types": [ 11 | "node", 12 | "mocha" 13 | ] 14 | }, 15 | "include": [ 16 | "src" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------