├── .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 | 
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 |
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 |
--------------------------------------------------------------------------------