├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .metadocrc ├── .prettierignore ├── .prettierrc ├── AUTHORS ├── LICENSE ├── README.md ├── doc ├── footer.md └── header.md ├── lib ├── adapters.js ├── array.js ├── async-iterator.js ├── collector.class.js ├── collector.functor.js ├── collector.js ├── collector.prototype.js ├── composition.js ├── control.js ├── do.js ├── fp.js ├── memoize.js ├── poolify.js ├── poolify.opt.js ├── poolify.symbol.js ├── queue.js └── throttle.js ├── metasync.js ├── package-lock.json ├── package.json ├── test ├── adapters.js ├── array.asyncMap.js ├── array.each.js ├── array.every.js ├── array.filter.js ├── array.find.js ├── array.map.js ├── array.reduce.js ├── array.reduceRight.js ├── array.series.js ├── array.some.js ├── collectors.js ├── compose.clone.js ├── compose.js ├── compose.then.js ├── composition.cancel.js ├── composition.js ├── composition.parallel.js ├── composition.pause.js ├── composition.sequential.js ├── control.js ├── do.js ├── examples.js ├── firstOf.js ├── fp.ap.js ├── fp.asAsync.js ├── fp.concat.js ├── fp.fmap.js ├── fp.of.js ├── memoize.js ├── poolify.js ├── queue.both.js ├── queue.js ├── queue.lifo.js ├── queue.modes.js ├── queue.pipe.js ├── queue.priority.js ├── queue.roundRobin.js └── throttle.js └── tests ├── async-iterator.js ├── fixtures ├── iterator.js └── throttle.js └── load ├── benchmark.js ├── collect.class.js ├── collect.functor.js ├── collect.js ├── collect.prototype.js ├── parallel.collect.js ├── parallel.compose.js ├── parallel.promise.js ├── poolify.array.js ├── poolify.opt.js ├── poolify.symbol.js ├── run.sh ├── sequential.compose.js └── sequential.promise.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | .nyc_output 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["metarhia", "plugin:prettier/recommended"], 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "rules": { 12 | "no-invalid-this": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: usage example or test. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Desktop (please complete the following information):** 22 | 23 | - OS: [e.g. Fedora 30 64-bit] 24 | - Node.js version [e.g. 14.15.1] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Please don't open an issue to ask questions 4 | --- 5 | 6 | Issues on GitHub are intended to be related to problems and feature requests 7 | so we recommend not using this medium to ask them here grin. Thanks for 8 | understanding! 9 | 10 | If you have a question, please check out our support groups and channels for 11 | developers community: 12 | 13 | Telegram: 14 | 15 | - Channel for Metarhia community: https://t.me/metarhia 16 | - Group for Metarhia technology stack community: https://t.me/metaserverless 17 | - Group for NodeUA community: https://t.me/nodeua 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | - [ ] tests and linter show no problems (`npm t`) 8 | - [ ] tests are added/updated for bug fixes and new features 9 | - [ ] code is properly formatted (`npm run fmt`) 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | node: 12 | - 14 13 | - 16 14 | - 18 15 | - 19 16 | - 20 17 | os: 18 | - ubuntu-latest 19 | - windows-latest 20 | - macos-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node }} 28 | - uses: actions/cache@v2 29 | with: 30 | path: ~/.npm 31 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-node- 34 | - run: npm ci 35 | - run: npm test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | .DS_Store 5 | coverage 6 | .nyc_output 7 | -------------------------------------------------------------------------------- /.metadocrc: -------------------------------------------------------------------------------- 1 | { 2 | "headerFile": "doc/header.md", 3 | "footerFile": "doc/footer.md", 4 | "minHeaderLevel": 2, 5 | "removeInterface": true, 6 | "files": [ 7 | "lib/adapters.js", 8 | "lib/array.js", 9 | "lib/async-iterator.js", 10 | "lib/collector.js", 11 | "lib/composition.js", 12 | "lib/control.js", 13 | "lib/do.js", 14 | "lib/fp.js", 15 | "lib/memoize.js", 16 | "lib/poolify.js", 17 | "lib/queue.js", 18 | "lib/throttle.js" 19 | ], 20 | "customLinks": { 21 | "AsyncIterable": "https://tc39.github.io/ecma262/#sec-asynciterable-interface", 22 | "AsyncIterator": "#class-asynciterator", 23 | "ArrayChain": "#class-arraychain", 24 | "Collector": "#class-collector", 25 | "Composition": "#class-composition", 26 | "Memoized": "#class-memoized", 27 | "Queue": "#class-queue" 28 | }, 29 | "outputFile": "README.md" 30 | } 31 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "overrides": [ 5 | { 6 | "files": ["**/.*rc", "**/*.json"], 7 | "options": { "parser": "json" } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Timur Shemsedinov 2 | Alexey Orlenko 3 | Vlad Dziuba 4 | Dmytro Nechai 5 | Oleksandr Kovalchuk 6 | Vladyslav Dukhin 7 | Arthur Myronenko 8 | Alexey Kachan 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2022 Metarhia contributors 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 | -------------------------------------------------------------------------------- /doc/footer.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | - Timur Shemsedinov (marcusaurelius) 4 | - See github for full [contributors list](https://github.com/metarhia/metasync/graphs/contributors) 5 | -------------------------------------------------------------------------------- /doc/header.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Programming Library 2 | 3 | [![TravisCI](https://travis-ci.org/metarhia/metasync.svg?branch=master)](https://travis-ci.org/metarhia/metasync) 4 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/60fe108b31614b4191cd557d49112169)](https://www.codacy.com/app/metarhia/metasync) 5 | [![NPM Version](https://badge.fury.io/js/metasync.svg)](https://badge.fury.io/js/metasync) 6 | [![NPM Downloads/Month](https://img.shields.io/npm/dm/metasync.svg)](https://www.npmjs.com/package/metasync) 7 | [![NPM Downloads](https://img.shields.io/npm/dt/metasync.svg)](https://www.npmjs.com/package/metasync) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | $ npm install metasync 13 | ``` 14 | 15 | ## Asynchronous functions composition 16 | 17 | `metasync(fns)(data, done)` 18 | 19 | - `fns` - array of callback-last functions, callback contranct err-first 20 | - `data` - input data (optional) 21 | - `done` - err-first callback 22 | - Returns: composed callback-last / err-first function 23 | 24 | ![composition](https://cloud.githubusercontent.com/assets/4405297/16968374/1b81f160-4e17-11e6-96fa-9d7e2b422396.png) 25 | 26 | ```js 27 | const composed = metasync([f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]); 28 | ``` 29 | 30 | - Array of functions gives sequential execution: `[f1, f2, f3]` 31 | - Double brackets array of functions gives parallel execution: `[[f1, f2, f3]]` 32 | 33 | _Example:_ 34 | 35 | ```js 36 | const metasync = require('metasync'); 37 | const fs = require('fs'); 38 | 39 | // Data collector (collect keys by count) 40 | const dc = metasync.collect(4); 41 | 42 | dc.pick('user', { name: 'Marcus Aurelius' }); 43 | fs.readFile('HISTORY.md', (err, data) => dc.collect('history', err, data)); 44 | dc.take('readme', fs.readFile, 'README.md'); 45 | setTimeout(() => dc.pick('timer', { date: new Date() }), 1000); 46 | 47 | // Key collector (collect certain keys by names) 48 | const kc = metasync 49 | .collect(['user', 'history', 'readme', 'timer']) 50 | .timeout(2000) 51 | .distinct() 52 | .done((err, data) => console.log(data)); 53 | 54 | kc.pick('user', { name: 'Marcus Aurelius' }); 55 | kc.take('history', fs.readFile, 'HISTORY.md'); 56 | kc.take('readme', fs.readFile, 'README.md'); 57 | setTimeout(() => kc.pick('timer', { date: new Date() }), 1000); 58 | ``` 59 | 60 | ## API 61 | -------------------------------------------------------------------------------- /lib/adapters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Convert Promise to callback-last 4 | // promise 5 | // callback 6 | const promiseToCallbackLast = (promise) => (callback) => { 7 | promise.then( 8 | (value) => { 9 | callback(null, value); 10 | }, 11 | (reason) => { 12 | callback(reason); 13 | }, 14 | ); 15 | }; 16 | 17 | // Convert Promise-returning to callback-last / error-first contract 18 | // fn promise-returning function 19 | // 20 | // Returns: 21 | const callbackify = 22 | (fn) => 23 | (...args) => { 24 | const callback = args.pop(); 25 | promiseToCallbackLast(fn(...args))(callback); 26 | }; 27 | 28 | // Convert sync function to callback-last / error-first contract 29 | // fn regular synchronous function 30 | // 31 | // Returns: with contract: callback-last / error-first 32 | const asyncify = 33 | (fn) => 34 | (...args) => { 35 | const callback = args.pop(); 36 | setTimeout(() => { 37 | let result; 38 | try { 39 | result = fn(...args); 40 | } catch (error) { 41 | return void callback(error); 42 | } 43 | callback(null, result); 44 | }, 0); 45 | }; 46 | 47 | // Convert async function to Promise-returning function 48 | // fn callback-last function 49 | // 50 | // Returns: Promise-returning function 51 | const promisify = 52 | (fn) => 53 | (...args) => 54 | new Promise((resolve, reject) => { 55 | fn(...args, (err, data) => { 56 | if (err) reject(err); 57 | else resolve(data); 58 | }); 59 | }); 60 | 61 | // Convert sync function to Promise object 62 | // fn regular synchronous function 63 | // 64 | // Returns: Promise-returning function 65 | const promisifySync = 66 | (fn) => 67 | (...args) => { 68 | let result; 69 | try { 70 | result = fn(...args); 71 | } catch (error) { 72 | return Promise.reject(error); 73 | } 74 | return Promise.resolve(result); 75 | }; 76 | 77 | module.exports = { 78 | callbackify, 79 | asyncify, 80 | promiseToCallbackLast, 81 | promisify, 82 | promisifySync, 83 | }; 84 | -------------------------------------------------------------------------------- /lib/array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Asynchronous map (iterate parallel) 4 | // items - , incoming 5 | // fn - , to be executed for each value in the array 6 | // current - , current element being processed in the array 7 | // callback - 8 | // err - | 9 | // value - 10 | // done - , on done 11 | // err - | 12 | // result - 13 | const map = (items, fn, done) => { 14 | const len = items.length; 15 | if (!len) return void done(null, []); 16 | let errored = false; 17 | let count = 0; 18 | const result = new Array(len); 19 | 20 | const next = (index, err, value) => { 21 | if (errored) return; 22 | if (err) { 23 | errored = true; 24 | return void done(err); 25 | } 26 | result[index] = value; 27 | count++; 28 | if (count === len) done(null, result); 29 | }; 30 | 31 | for (let i = 0; i < len; i++) { 32 | fn(items[i], next.bind(null, i)); 33 | } 34 | }; 35 | 36 | const DEFAULT_OPTIONS = { min: 5, percent: 0.7 }; 37 | 38 | // Non-blocking synchronous map 39 | // Signature: items, fn[, options][, done] 40 | // items - , incoming dataset 41 | // fn - 42 | // item - 43 | // index - 44 | // options - , map params, optional 45 | // min - , min number of items in one next call 46 | // percent - , ratio of map time to all time 47 | // done - , call on done, optional 48 | // err - | 49 | // result - 50 | const asyncMap = (items, fn, options = {}, done) => { 51 | if (typeof options === 'function') { 52 | done = options; 53 | options = DEFAULT_OPTIONS; 54 | } 55 | 56 | if (!items.length) { 57 | if (done) done(null, []); 58 | return; 59 | } 60 | 61 | const min = options.min || DEFAULT_OPTIONS.min; 62 | const percent = options.percent || DEFAULT_OPTIONS.percent; 63 | 64 | let begin; 65 | let sum = 0; 66 | let count = 0; 67 | 68 | const result = done ? new Array(items.length) : null; 69 | const ratio = percent / (1 - percent); 70 | 71 | const countNumber = () => { 72 | const loopTime = Date.now() - begin; 73 | const itemTime = sum / count; 74 | const necessaryNumber = (ratio * loopTime) / itemTime; 75 | return Math.max(necessaryNumber, min); 76 | }; 77 | 78 | const next = () => { 79 | const itemsNumber = count ? countNumber() : min; 80 | const iterMax = Math.min(items.length, itemsNumber + count); 81 | 82 | begin = Date.now(); 83 | for (; count < iterMax; count++) { 84 | const itemResult = fn(items[count], count); 85 | if (done) result[count] = itemResult; 86 | } 87 | sum += Date.now() - begin; 88 | 89 | if (count < items.length) { 90 | begin = Date.now(); 91 | setTimeout(next, 0); 92 | } else if (done) { 93 | done(null, result); 94 | } 95 | }; 96 | 97 | next(); 98 | }; 99 | 100 | // Asynchrous filter (iterate parallel) 101 | // items - , incoming 102 | // fn - , to be executed for each value in the array 103 | // value - , item from items array 104 | // callback - 105 | // err - | 106 | // accepted - 107 | // done - , on done 108 | // err - | 109 | // result - 110 | // 111 | // Example: 112 | // metasync.filter( 113 | // ['data', 'to', 'filter'], 114 | // (item, callback) => callback(item.length > 2), 115 | // (err, result) => console.dir(result) 116 | // ); 117 | const filter = (items, fn, done) => { 118 | const len = items.length; 119 | 120 | if (!len) return void done(null, []); 121 | 122 | let count = 0; 123 | let suitable = 0; 124 | const data = new Array(len); 125 | const rejected = Symbol('rejected'); 126 | 127 | const next = (index, err, accepted) => { 128 | if (!accepted || err) { 129 | data[index] = rejected; 130 | } else { 131 | data[index] = items[index]; 132 | suitable++; 133 | } 134 | count++; 135 | if (count === len) { 136 | const result = new Array(suitable); 137 | let pos = 0; 138 | for (let i = 0; i < len; i++) { 139 | const val = data[i]; 140 | if (val !== rejected) result[pos++] = val; 141 | } 142 | done(null, result); 143 | } 144 | }; 145 | 146 | for (let i = 0; i < len; i++) { 147 | fn(items[i], next.bind(null, i)); 148 | } 149 | }; 150 | 151 | const REDUCE_EMPTY_ARR = 152 | 'Metasync: reduce of empty array with no initial value'; 153 | 154 | // Asynchronous reduce 155 | // Signature: items, fn, done[, initial] 156 | // items - , incoming 157 | // fn - , to be executed for each value in array 158 | // previous - , value previously returned in the last iteration 159 | // current - , current element being processed in the array 160 | // callback - , callback for returning value 161 | // back to reduce function 162 | // err - | 163 | // data - , resulting value 164 | // counter - , index of the current element 165 | // being processed in array 166 | // items - , the array reduce was called upon 167 | // done - , on done 168 | // err - | 169 | // result - 170 | // initial - , optional value to be used as first 171 | // argument in first iteration 172 | const reduce = (items, fn, done, initial) => { 173 | const len = items.length; 174 | const hasInitial = typeof initial !== 'undefined'; 175 | 176 | if (len === 0 && !hasInitial) 177 | return void done(new TypeError(REDUCE_EMPTY_ARR), initial); 178 | 179 | let previous = hasInitial ? initial : items[0]; 180 | if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) 181 | return void done(null, previous); 182 | 183 | let count = hasInitial ? 0 : 1; 184 | let current = items[count]; 185 | const last = len - 1; 186 | 187 | const next = (err, data) => { 188 | if (err) return void done(err); 189 | if (count === last) return void done(null, data); 190 | count++; 191 | previous = data; 192 | current = items[count]; 193 | fn(previous, current, next, count, items); 194 | }; 195 | 196 | fn(previous, current, next, count, items); 197 | }; 198 | 199 | const REDUCE_RIGHT_EMPTY_ARR = 200 | 'Metasync: reduceRight of empty array with no initial value'; 201 | 202 | // Asynchronous reduceRight 203 | // Signature: items, fn, done[, initial] 204 | // items - , incoming 205 | // fn - , to be executed for each value in array 206 | // previous - , value previously returned in the last iteration 207 | // current - , current element being processed in the array 208 | // callback - , callback for returning value 209 | // back to reduce function 210 | // err - | 211 | // data - , resulting value 212 | // counter - , index of the current element 213 | // being processed in array 214 | // items - , the array reduce was called upon 215 | // done - , on done 216 | // err - | 217 | // result - 218 | // initial - , optional value to be used as first 219 | // argument in first iteration 220 | const reduceRight = (items, fn, done, initial) => { 221 | const len = items.length; 222 | const hasInitial = typeof initial !== 'undefined'; 223 | 224 | if (len === 0 && !hasInitial) 225 | return void done(new TypeError(REDUCE_RIGHT_EMPTY_ARR), initial); 226 | 227 | let previous = hasInitial ? initial : items[len - 1]; 228 | if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) 229 | return void done(null, previous); 230 | 231 | let count = hasInitial ? len - 1 : len - 2; 232 | let current = items[count]; 233 | const last = 0; 234 | 235 | const next = (err, data) => { 236 | if (err) return void done(err); 237 | if (count === last) return void done(null, data); 238 | count--; 239 | previous = data; 240 | current = items[count]; 241 | fn(previous, current, next, count, items); 242 | }; 243 | 244 | fn(previous, current, next, count, items); 245 | }; 246 | 247 | // Asynchronous each (iterate in parallel) 248 | // items - , incoming 249 | // fn - 250 | // value - , item from items array 251 | // callback - 252 | // err - | 253 | // done - , on done 254 | // err - | 255 | // items - 256 | // 257 | // Example: 258 | // metasync.each( 259 | // ['a', 'b', 'c'], 260 | // (item, callback) => { 261 | // console.dir({ each: item }); 262 | // callback(); 263 | // }, 264 | // (err, data) => console.dir('each done') 265 | // ); 266 | const each = (items, fn, done) => { 267 | const len = items.length; 268 | if (len === 0) return void done(null, items); 269 | let count = 0; 270 | let errored = false; 271 | 272 | const next = (err) => { 273 | if (errored) return; 274 | if (err) { 275 | errored = true; 276 | return void done(err); 277 | } 278 | count++; 279 | if (count === len) done(null); 280 | }; 281 | 282 | for (let i = 0; i < len; i++) { 283 | fn(items[i], next); 284 | } 285 | }; 286 | 287 | // Asynchronous series 288 | // items - , incoming 289 | // fn - 290 | // value - , item from items array 291 | // callback - 292 | // err - | 293 | // done - , on done 294 | // err - | 295 | // items - 296 | // 297 | // Example: 298 | // metasync.series( 299 | // ['a', 'b', 'c'], 300 | // (item, callback) => { 301 | // console.dir({ series: item }); 302 | // callback(); 303 | // }, 304 | // (err, data) => { 305 | // console.dir('series done'); 306 | // } 307 | // ); 308 | const series = (items, fn, done) => { 309 | const len = items.length; 310 | let i = -1; 311 | 312 | const next = () => { 313 | i++; 314 | if (i === len) return void done(null, items); 315 | fn(items[i], (err) => { 316 | if (err) return void done(err); 317 | setImmediate(next); 318 | }); 319 | }; 320 | next(); 321 | }; 322 | 323 | // Asynchronous find (iterate in series) 324 | // items - , incoming 325 | // fn - , 326 | // value - , item from items array 327 | // callback - 328 | // err - | 329 | // accepted - 330 | // done - , on done 331 | // err - | 332 | // result - 333 | // 334 | // Example: 335 | // metasync.find( 336 | // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 337 | // (item, callback) => callback(null, item % 3 === 0 && item % 5 === 0), 338 | // (err, result) => { 339 | // console.dir(result); 340 | // } 341 | // ); 342 | const find = (items, fn, done) => { 343 | const len = items.length; 344 | if (len === 0) return void done(); 345 | let finished = false; 346 | const last = len - 1; 347 | 348 | const next = (index, err, accepted) => { 349 | if (finished) return; 350 | if (err) { 351 | finished = true; 352 | return void done(err); 353 | } 354 | if (accepted) { 355 | finished = true; 356 | return void done(null, items[index]); 357 | } 358 | if (index === last) done(null); 359 | }; 360 | 361 | for (let i = 0; i < len; i++) { 362 | fn(items[i], next.bind(null, i)); 363 | } 364 | }; 365 | 366 | // Asynchronous every 367 | // items - , incoming 368 | // fn - , 369 | // value - , item from items array 370 | // callback - 371 | // err - | 372 | // accepted - 373 | // done - , on done 374 | // err - | 375 | // result - 376 | const every = (items, fn, done) => { 377 | if (items.length === 0) return void done(null, true); 378 | let proceedItemsCount = 0; 379 | const len = items.length; 380 | 381 | const finish = (err, accepted) => { 382 | if (!done) return; 383 | if (err || !accepted) { 384 | done(err, false); 385 | done = null; 386 | return; 387 | } 388 | proceedItemsCount++; 389 | if (proceedItemsCount === len) done(null, true); 390 | }; 391 | 392 | for (const item of items) fn(item, finish); 393 | }; 394 | 395 | // Asynchronous some (iterate in series) 396 | // items - , incoming 397 | // fn - 398 | // value - , item from items array 399 | // callback - 400 | // err - | 401 | // accepted - 402 | // done - , on done 403 | // err - | 404 | // result - 405 | const some = (items, fn, done) => { 406 | const len = items.length; 407 | let i = 0; 408 | 409 | const next = () => { 410 | if (i === len) return void done(null, false); 411 | fn(items[i], (err, accepted) => { 412 | if (err) return void done(err); 413 | if (accepted) return void done(null, true); 414 | i++; 415 | next(); 416 | }); 417 | }; 418 | 419 | if (len > 0) next(); 420 | else done(null, false); 421 | }; 422 | 423 | module.exports = { 424 | map, 425 | filter, 426 | reduce, 427 | reduceRight, 428 | each, 429 | series, 430 | find, 431 | every, 432 | some, 433 | asyncMap, 434 | }; 435 | -------------------------------------------------------------------------------- /lib/async-iterator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | 3 | 'use strict'; 4 | 5 | const { promisify } = require('util'); 6 | 7 | const timeout = promisify((res) => setTimeout(res, 0)); 8 | 9 | const toIterator = (base) => { 10 | if (base[Symbol.asyncIterator]) { 11 | return base[Symbol.asyncIterator](); 12 | } else if (base[Symbol.iterator]) { 13 | return base[Symbol.iterator](); 14 | } else { 15 | throw new TypeError('Base is not Iterable'); 16 | } 17 | }; 18 | 19 | class AsyncIterator { 20 | constructor(base) { 21 | this.base = toIterator(base); 22 | } 23 | 24 | [Symbol.asyncIterator]() { 25 | return this; 26 | } 27 | 28 | async next() { 29 | return this.base.next(); 30 | } 31 | 32 | async count() { 33 | let count = 0; 34 | while (!(await this.next()).done) { 35 | count++; 36 | } 37 | return count; 38 | } 39 | 40 | async each(fn, thisArg) { 41 | return this.forEach(fn, thisArg); 42 | } 43 | 44 | async forEach(fn, thisArg) { 45 | for await (const value of this) { 46 | await fn.call(thisArg, value); 47 | } 48 | } 49 | 50 | async parallel(fn, thisArg) { 51 | const promises = []; 52 | for await (const value of this) { 53 | promises.push(fn.call(thisArg, value)); 54 | } 55 | return Promise.all(promises); 56 | } 57 | 58 | async every(predicate, thisArg) { 59 | for await (const value of this) { 60 | const res = await predicate.call(thisArg, value); 61 | if (!res) return false; 62 | } 63 | return true; 64 | } 65 | 66 | async find(predicate, thisArg) { 67 | for await (const value of this) { 68 | if (await predicate.call(thisArg, value)) { 69 | return value; 70 | } 71 | } 72 | const value = undefined; 73 | return value; 74 | } 75 | 76 | async includes(element) { 77 | for await (const value of this) { 78 | if (value === element || (Number.isNaN(value) && Number.isNaN(element))) { 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | async reduce(reducer, initialValue) { 86 | let result = initialValue; 87 | 88 | if (result === undefined) { 89 | const next = await this.next(); 90 | if (next.done) { 91 | throw new TypeError( 92 | 'Reduce of consumed async iterator with no initial value', 93 | ); 94 | } 95 | result = next.value; 96 | } 97 | 98 | for await (const value of this) { 99 | result = await reducer(result, value); 100 | } 101 | return result; 102 | } 103 | 104 | async some(predicate, thisArg) { 105 | for await (const value of this) { 106 | if (await predicate.call(thisArg, value)) { 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | async someCount(predicate, count, thisArg) { 114 | let n = 0; 115 | for await (const value of this) { 116 | if (await predicate.call(thisArg, value)) { 117 | if (++n === count) return true; 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | async collectTo(CollectionClass) { 124 | const arr = await this.toArray(); 125 | return new CollectionClass(arr); 126 | } 127 | 128 | async collectWith(obj, collector) { 129 | await this.forEach((element) => collector(obj, element)); 130 | } 131 | 132 | async join(sep = ',', prefix = '', suffix = '') { 133 | let result = prefix; 134 | const { done, value } = await this.next(); 135 | if (!done) { 136 | result += value; 137 | for await (const value of this) { 138 | result += sep + value; 139 | } 140 | } 141 | return result + suffix; 142 | } 143 | 144 | async toArray() { 145 | const newArray = []; 146 | for await (const value of this) { 147 | newArray.push(value); 148 | } 149 | return newArray; 150 | } 151 | 152 | map(mapper, thisArg) { 153 | return new MapIterator(this, mapper, thisArg); 154 | } 155 | 156 | filter(predicate, thisArg) { 157 | return new FilterIterator(this, predicate, thisArg); 158 | } 159 | 160 | flat(depth = 1) { 161 | return new FlatIterator(this, depth); 162 | } 163 | 164 | flatMap(mapper, thisArg) { 165 | return new FlatMapIterator(this, mapper, thisArg); 166 | } 167 | 168 | zip(...iterators) { 169 | return new ZipIterator(this, iterators); 170 | } 171 | 172 | chain(...iterators) { 173 | return new ChainIterator(this, iterators); 174 | } 175 | 176 | take(amount) { 177 | return new TakeIterator(this, amount); 178 | } 179 | 180 | takeWhile(predicate, thisArg) { 181 | return new TakeWhileIterator(this, predicate, thisArg); 182 | } 183 | 184 | skip(amount) { 185 | for (let i = 0; i < amount; i++) { 186 | this.next(); 187 | } 188 | return this; 189 | } 190 | 191 | throttle(percent, min) { 192 | return new ThrottleIterator(this, percent, min); 193 | } 194 | 195 | enumerate() { 196 | return new EnumerateIterator(this); 197 | } 198 | } 199 | 200 | class MapIterator extends AsyncIterator { 201 | constructor(base, mapper, thisArg) { 202 | super(base); 203 | this.mapper = mapper; 204 | this.thisArg = thisArg; 205 | } 206 | 207 | async next() { 208 | const { done, value } = await this.base.next(); 209 | return { 210 | done, 211 | value: done ? undefined : await this.mapper.call(this.thisArg, value), 212 | }; 213 | } 214 | } 215 | 216 | class FilterIterator extends AsyncIterator { 217 | constructor(base, predicate, thisArg) { 218 | super(base); 219 | this.predicate = predicate; 220 | this.thisArg = thisArg; 221 | } 222 | 223 | async next() { 224 | for await (const value of this.base) { 225 | if (await this.predicate.call(this.thisArg, value)) { 226 | return { done: false, value }; 227 | } 228 | } 229 | return { done: true, value: undefined }; 230 | } 231 | } 232 | 233 | class FlatIterator extends AsyncIterator { 234 | constructor(base, depth) { 235 | super(base); 236 | this.currentDepth = 0; 237 | this.stack = new Array(depth + 1); 238 | this.stack[0] = base; 239 | } 240 | 241 | async next() { 242 | while (this.currentDepth >= 0) { 243 | const top = this.stack[this.currentDepth]; 244 | const next = await top.next(); 245 | 246 | if (next.done) { 247 | this.stack[this.currentDepth] = null; 248 | this.currentDepth--; 249 | continue; 250 | } 251 | 252 | if ( 253 | this.currentDepth === this.stack.length - 1 || 254 | (!next.value[Symbol.iterator] && !next.value[Symbol.asyncIterator]) 255 | ) { 256 | return next; 257 | } 258 | 259 | this.stack[++this.currentDepth] = next.value[Symbol.asyncIterator] 260 | ? next.value[Symbol.asyncIterator]() 261 | : next.value[Symbol.iterator](); 262 | } 263 | 264 | return { done: true, value: undefined }; 265 | } 266 | } 267 | 268 | class FlatMapIterator extends AsyncIterator { 269 | constructor(base, mapper, thisArg) { 270 | super(base); 271 | this.mapper = mapper; 272 | this.thisArg = thisArg; 273 | this.currentIterator = null; 274 | } 275 | 276 | async next() { 277 | if (!this.currentIterator) { 278 | const next = await this.base.next(); 279 | if (next.done) { 280 | return next; 281 | } 282 | 283 | const value = this.mapper.call(this.thisArg, next.value); 284 | if (!value[Symbol.iterator] && !value[Symbol.asyncIterator]) { 285 | return { done: false, value }; 286 | } 287 | 288 | this.currentIterator = toIterator(value); 289 | } 290 | 291 | const next = await this.currentIterator.next(); 292 | 293 | if (next.done) { 294 | this.currentIterator = null; 295 | return this.next(); 296 | } 297 | return next; 298 | } 299 | } 300 | 301 | class TakeIterator extends AsyncIterator { 302 | constructor(base, amount) { 303 | super(base); 304 | this.amount = amount; 305 | this.iterated = 0; 306 | } 307 | 308 | async next() { 309 | this.iterated++; 310 | if (this.iterated <= this.amount) { 311 | return this.base.next(); 312 | } 313 | return { done: true, value: undefined }; 314 | } 315 | } 316 | 317 | class TakeWhileIterator extends AsyncIterator { 318 | constructor(base, predicate, thisArg) { 319 | super(base); 320 | this.predicate = predicate; 321 | this.thisArg = thisArg; 322 | this.done = false; 323 | } 324 | 325 | async next() { 326 | if (this.done) return { done: true, value: undefined }; 327 | const next = await this.base.next(); 328 | const res = await this.predicate.call(this.thisArg, next.value); 329 | if (!next.done && res) return next; 330 | this.done = true; 331 | return { done: true, value: undefined }; 332 | } 333 | } 334 | 335 | class ZipIterator extends AsyncIterator { 336 | constructor(base, iterators) { 337 | super(base); 338 | this.iterators = iterators.map(toIterator); 339 | } 340 | 341 | async next() { 342 | const result = []; 343 | 344 | const next = await this.base.next(); 345 | if (next.done) { 346 | return next; 347 | } 348 | result.push(next.value); 349 | 350 | for (const iterator of this.iterators) { 351 | const next = await iterator.next(); 352 | 353 | if (next.done) { 354 | return next; 355 | } 356 | result.push(next.value); 357 | } 358 | return { done: false, value: result }; 359 | } 360 | } 361 | 362 | class ChainIterator extends AsyncIterator { 363 | constructor(base, iterators) { 364 | super(base); 365 | this.currentIterator = base; 366 | this.iterators = iterators.map(toIterator)[Symbol.iterator](); 367 | } 368 | 369 | async next() { 370 | const next = await this.currentIterator.next(); 371 | if (!next.done) { 372 | return next; 373 | } 374 | const iterator = this.iterators.next(); 375 | if (iterator.done) { 376 | return iterator; 377 | } 378 | this.currentIterator = iterator.value; 379 | return this.next(); 380 | } 381 | } 382 | 383 | class EnumerateIterator extends AsyncIterator { 384 | constructor(base) { 385 | super(base); 386 | this.index = 0; 387 | } 388 | 389 | async next() { 390 | const next = await this.base.next(); 391 | if (next.done) { 392 | return next; 393 | } 394 | return { done: false, value: [this.index++, next.value] }; 395 | } 396 | } 397 | 398 | class ThrottleIterator extends AsyncIterator { 399 | constructor(base, percent = 0.7, min = 5) { 400 | super(base); 401 | this.min = min; 402 | this.ratio = percent / (1 - percent); 403 | 404 | this.sum = 0; 405 | this.count = 0; 406 | this.begin = Date.now(); 407 | this.iterMax = this.min; 408 | } 409 | 410 | async next() { 411 | if (this.iterMax > this.count) { 412 | this.count++; 413 | return this.base.next(); 414 | } 415 | 416 | this.sum += Date.now() - this.begin; 417 | const itemTime = this.sum / this.count; 418 | 419 | this.begin = Date.now(); 420 | await timeout(); 421 | const loopTime = Date.now() - this.begin; 422 | 423 | const number = Math.max((this.ratio * loopTime) / itemTime, this.min); 424 | 425 | this.iterMax = Math.round(number) + this.count; 426 | 427 | this.count++; 428 | this.begin = Date.now(); 429 | return this.base.next(); 430 | } 431 | } 432 | 433 | // Create an AsyncIterator instance 434 | // base - | , an iterable 435 | // that is wrapped in 436 | // 437 | // Returns: 438 | const asyncIter = (base) => new AsyncIterator(base); 439 | 440 | module.exports = { asyncIter, AsyncIterator }; 441 | -------------------------------------------------------------------------------- /lib/collector.class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('@metarhia/common'); 4 | 5 | const UNEXPECTED_KEY = 'Metasync: unexpected key: '; 6 | const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; 7 | const COLLECT_CANCELED = 'Metasync: Collector cancelled'; 8 | 9 | class Collector { 10 | // Collector constructor 11 | // expected - | , count or keys 12 | constructor(expected) { 13 | this.expectKeys = Array.isArray(expected) ? new Set(expected) : null; 14 | this.expected = this.expectKeys ? expected.length : expected; 15 | this.keys = new Set(); 16 | this.count = 0; 17 | this.timer = null; 18 | this.onDone = common.emptiness; 19 | this.isDistinct = false; 20 | this.isDone = false; 21 | this.data = {}; 22 | } 23 | 24 | collect(key, err, value) { 25 | if (this.isDone) return this; 26 | if (err) { 27 | this.finalize(err, this.data); 28 | return this; 29 | } 30 | if (this.expectKeys && !this.expectKeys.has(key)) { 31 | if (this.isDistinct) { 32 | const err = new Error(UNEXPECTED_KEY + key); 33 | this.finalize(err, this.data); 34 | return this; 35 | } 36 | } else if (!this.keys.has(key)) { 37 | this.count++; 38 | } 39 | this.data[key] = value; 40 | this.keys.add(key); 41 | if (this.expected === this.count) { 42 | this.finalize(null, this.data); 43 | } 44 | return this; 45 | } 46 | 47 | pick(key, value) { 48 | this.collect(key, null, value); 49 | return this; 50 | } 51 | 52 | fail(key, err) { 53 | this.collect(key, err); 54 | return this; 55 | } 56 | 57 | take(key, fn, ...args) { 58 | fn(...args, (err, data) => { 59 | this.collect(key, err, data); 60 | }); 61 | return this; 62 | } 63 | 64 | timeout(msec) { 65 | if (this.timer) { 66 | clearTimeout(this.timer); 67 | this.timer = null; 68 | } 69 | if (msec > 0) { 70 | this.timer = setTimeout(() => { 71 | const err = new Error(COLLECT_TIMEOUT); 72 | this.finalize(err, this.data); 73 | }, msec); 74 | } 75 | return this; 76 | } 77 | 78 | done(callback) { 79 | this.onDone = callback; 80 | return this; 81 | } 82 | 83 | finalize(key, err, data) { 84 | if (this.isDone) return this; 85 | if (this.onDone) { 86 | if (this.timer) { 87 | clearTimeout(this.timer); 88 | this.timer = null; 89 | } 90 | this.isDone = true; 91 | this.onDone(key, err, data); 92 | } 93 | return this; 94 | } 95 | 96 | distinct(value = true) { 97 | this.isDistinct = value; 98 | return this; 99 | } 100 | 101 | cancel(err) { 102 | err = err || new Error(COLLECT_CANCELED); 103 | this.finalize(err, this.data); 104 | return this; 105 | } 106 | 107 | then(fulfilled, rejected) { 108 | const fulfill = common.once(fulfilled); 109 | const reject = common.once(rejected); 110 | this.onDone = (err, result) => { 111 | if (err) reject(err); 112 | else fulfill(result); 113 | }; 114 | return this; 115 | } 116 | } 117 | 118 | // Collector instance constructor 119 | // expected - | 120 | // 121 | // Returns: 122 | const collect = (expected) => new Collector(expected); 123 | 124 | module.exports = { collect }; 125 | -------------------------------------------------------------------------------- /lib/collector.functor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TYPE_ERROR = 'Metasync: Collect unexpected type'; 4 | const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; 5 | 6 | // Collector instance constructor 7 | // expected - | , count or keys 8 | // 9 | // Returns: , functor, collector 10 | const collect = (expected) => { 11 | const isCount = typeof expected === 'number'; 12 | const isKeys = Array.isArray(expected); 13 | if (!(isCount || isKeys)) throw new TypeError(TYPE_ERROR); 14 | let keys = null; 15 | if (isKeys) { 16 | keys = new Set(expected); 17 | expected = expected.length; 18 | } 19 | let count = 0; 20 | let timer = null; 21 | let onDone = null; 22 | let isDistinct = false; 23 | let isDone = false; 24 | const data = {}; 25 | 26 | const collector = (key, err, value) => { 27 | if (isDone) return collector; 28 | if (!isDistinct || !(key in data)) { 29 | if (!isCount && !keys.has(key)) return collector; 30 | count++; 31 | } 32 | if (err) { 33 | collector.finalize(err, data); 34 | return collector; 35 | } 36 | data[key] = value; 37 | if (expected === count) { 38 | if (timer) clearTimeout(timer); 39 | collector.finalize(null, data); 40 | } 41 | return collector; 42 | }; 43 | 44 | const methods = { 45 | pick: (key, value) => collector(key, null, value), 46 | fail: (key, err) => collector(key, err), 47 | 48 | take: (key, fn, ...args) => { 49 | fn(...args, (err, data) => collector(key, err, data)); 50 | return collector; 51 | }, 52 | 53 | timeout: (msec) => { 54 | if (msec) { 55 | timer = setTimeout(() => { 56 | const err = new Error(COLLECT_TIMEOUT); 57 | collector.finalize(err, data); 58 | }, msec); 59 | timer.unref(); 60 | } 61 | return collector; 62 | }, 63 | 64 | // Call on done 65 | // callback - 66 | // error - | 67 | // data - 68 | done: (callback) => { 69 | onDone = callback; 70 | return collector; 71 | }, 72 | 73 | finalize: (err, data) => { 74 | if (isDone) return collector; 75 | isDone = true; 76 | if (onDone) onDone(err, data); 77 | return collector; 78 | }, 79 | 80 | distinct: (value = true) => { 81 | isDistinct = value; 82 | return collector; 83 | }, 84 | }; 85 | 86 | return Object.assign(collector, methods); 87 | }; 88 | 89 | module.exports = { collect }; 90 | -------------------------------------------------------------------------------- /lib/collector.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('@metarhia/common'); 4 | 5 | const UNEXPECTED_KEY = 'Metasync: unexpected key: '; 6 | const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; 7 | const COLLECT_CANCELED = 'Metasync: Collector cancelled'; 8 | 9 | // Data collector 10 | // expected - | , count or keys 11 | function Collector(expected) { 12 | this.expectKeys = Array.isArray(expected) ? new Set(expected) : null; 13 | this.expected = this.expectKeys ? expected.length : expected; 14 | this.keys = new Set(); 15 | this.count = 0; 16 | this.timer = null; 17 | this.onDone = common.emptiness; 18 | this.isDistinct = false; 19 | this.isDone = false; 20 | this.data = {}; 21 | } 22 | 23 | // Pick or fail key 24 | // key - 25 | // err - 26 | // value - 27 | // 28 | // Returns: 29 | Collector.prototype.collect = function (key, err, value) { 30 | if (this.isDone) return this; 31 | if (err) { 32 | this.finalize(err, this.data); 33 | return this; 34 | } 35 | if (this.expectKeys && !this.expectKeys.has(key)) { 36 | if (this.isDistinct) { 37 | const err = new Error(UNEXPECTED_KEY + key); 38 | this.finalize(err, this.data); 39 | return this; 40 | } 41 | } else if (!this.keys.has(key)) { 42 | this.count++; 43 | } 44 | this.data[key] = value; 45 | this.keys.add(key); 46 | if (this.expected === this.count) { 47 | this.finalize(null, this.data); 48 | } 49 | return this; 50 | }; 51 | 52 | // Pick key 53 | // key - 54 | // value - 55 | // 56 | // Returns: 57 | Collector.prototype.pick = function (key, value) { 58 | this.collect(key, null, value); 59 | return this; 60 | }; 61 | 62 | // Fail key 63 | // key - 64 | // err - 65 | // 66 | // Returns: 67 | Collector.prototype.fail = function (key, err) { 68 | this.collect(key, err); 69 | return this; 70 | }; 71 | 72 | // Take method result 73 | // key - 74 | // fn - 75 | // args - , rest arguments, to be passed in fn 76 | // 77 | // Returns: 78 | Collector.prototype.take = function (key, fn, ...args) { 79 | fn(...args, (err, data) => { 80 | this.collect(key, err, data); 81 | }); 82 | return this; 83 | }; 84 | 85 | // Set timeout 86 | // msec - 87 | // 88 | // Returns: 89 | Collector.prototype.timeout = function (msec) { 90 | if (this.timer) { 91 | clearTimeout(this.timer); 92 | this.timer = null; 93 | } 94 | if (msec > 0) { 95 | this.timer = setTimeout(() => { 96 | const err = new Error(COLLECT_TIMEOUT); 97 | this.finalize(err, this.data); 98 | }, msec); 99 | } 100 | return this; 101 | }; 102 | 103 | // Set on done listener 104 | // callback - 105 | // err - 106 | // data - 107 | // 108 | // Returns: 109 | Collector.prototype.done = function (callback) { 110 | this.onDone = callback; 111 | return this; 112 | }; 113 | 114 | Collector.prototype.finalize = function (key, err, data) { 115 | if (this.isDone) return this; 116 | if (this.timer) { 117 | clearTimeout(this.timer); 118 | this.timer = null; 119 | } 120 | this.isDone = true; 121 | this.onDone(key, err, data); 122 | return this; 123 | }; 124 | 125 | // Deny or allow unlisted keys 126 | // value - 127 | // 128 | // Returns: 129 | Collector.prototype.distinct = function (value = true) { 130 | this.isDistinct = value; 131 | return this; 132 | }; 133 | 134 | Collector.prototype.cancel = function (err) { 135 | err = err || new Error(COLLECT_CANCELED); 136 | this.finalize(err, this.data); 137 | return this; 138 | }; 139 | 140 | Collector.prototype.then = function (fulfill, reject) { 141 | if (!fulfill) fulfill = common.emptiness; 142 | if (!reject) reject = common.emptiness; 143 | this.onDone = (err, result) => { 144 | this.onDone = common.emptiness; 145 | if (err) reject(err); 146 | else fulfill(result); 147 | }; 148 | return this; 149 | }; 150 | 151 | // Create Collector instance 152 | // expected - | 153 | // 154 | // Returns: 155 | const collect = (expected) => new Collector(expected); 156 | 157 | module.exports = { collect, Collector }; 158 | -------------------------------------------------------------------------------- /lib/collector.prototype.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('@metarhia/common'); 4 | 5 | function Collector() {} 6 | 7 | const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; 8 | 9 | // Add event listener 10 | // eventName - 11 | // listener - , handler 12 | // 13 | // Example: 14 | // const collector = new Collector(); 15 | // collector.on('error', (err, key) => { ... }); 16 | // collector.on('timeout', (err, data) => { ... }); 17 | // collector.on('done', (errs, data) => { ... }) 18 | Collector.prototype.on = function (eventName, listener) { 19 | if (eventName in this.events) { 20 | this.events[eventName] = listener; 21 | } 22 | }; 23 | 24 | // Emit Collector events 25 | // eventName - 26 | // err - | 27 | Collector.prototype.emit = function (eventName, err, data) { 28 | const event = this.events[eventName]; 29 | if (event) event(err, data); 30 | }; 31 | 32 | // Create new DataCollector 33 | // Signature: expected[, timeout] 34 | // expected - , count of `collect()` calls expected 35 | // timeout - , collect timeout, optional 36 | // 37 | // Returns: 38 | const DataCollector = function (expected, timeout) { 39 | this.expected = expected; 40 | this.timeout = timeout; 41 | this.count = 0; 42 | this.data = {}; 43 | this.errs = []; 44 | this.events = { 45 | error: null, 46 | timeout: null, 47 | done: null, 48 | }; 49 | if (this.timeout) { 50 | this.timer = setTimeout(() => { 51 | const err = new Error(COLLECT_TIMEOUT); 52 | this.emit('timeout', err, this.data); 53 | }, timeout); 54 | } 55 | }; 56 | 57 | common.inherits(DataCollector, Collector); 58 | 59 | // Push data to collector 60 | // key - , key in result data 61 | // data - | , value or error 62 | DataCollector.prototype.collect = function (key, data) { 63 | this.count++; 64 | if (data instanceof Error) { 65 | this.errs[key] = data; 66 | this.emit('error', data, key); 67 | } else { 68 | this.data[key] = data; 69 | } 70 | if (this.expected === this.count) { 71 | if (this.timer) clearTimeout(this.timer); 72 | const errs = this.errs.length ? this.errs : null; 73 | this.emit('done', errs, this.data); 74 | } 75 | }; 76 | 77 | // Key Collector 78 | // Signature: keys[, timeout] 79 | // keys - 80 | // timeout - , collect timeout, optional 81 | // 82 | // Returns: 83 | // 84 | // Example: new KeyCollector(['config', 'users', 'cities']) 85 | const KeyCollector = function (keys, timeout) { 86 | this.isDone = false; 87 | this.keys = keys; 88 | this.expected = keys.length; 89 | this.count = 0; 90 | this.timeout = timeout; 91 | this.data = {}; 92 | this.errs = []; 93 | this.events = { 94 | error: null, 95 | timeout: null, 96 | done: null, 97 | }; 98 | const collector = this; 99 | if (this.timeout) { 100 | this.timer = setTimeout(() => { 101 | const err = new Error(COLLECT_TIMEOUT); 102 | collector.emit('timeout', err, collector.data); 103 | }, timeout); 104 | } 105 | }; 106 | 107 | common.inherits(KeyCollector, Collector); 108 | // Collect keys and data 109 | // key - 110 | // data - | | , value or error 111 | KeyCollector.prototype.collect = function (key, data) { 112 | if (this.keys.includes(key)) { 113 | this.count++; 114 | if (data instanceof Error) { 115 | this.errs[key] = data; 116 | this.emit('error', data, key); 117 | } else { 118 | this.data[key] = data; 119 | } 120 | if (this.expected === this.count) { 121 | if (this.timer) clearTimeout(this.timer); 122 | const errs = this.errs.length ? this.errs : null; 123 | this.emit('done', errs, this.data); 124 | } 125 | } 126 | }; 127 | 128 | KeyCollector.prototype.stop = function () {}; 129 | 130 | KeyCollector.prototype.pause = function () {}; 131 | 132 | KeyCollector.prototype.resume = function () {}; 133 | 134 | module.exports = { DataCollector, KeyCollector }; 135 | -------------------------------------------------------------------------------- /lib/composition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Composition() {} 4 | 5 | const COMPOSE_CANCELED = 'Metasync: asynchronous composition canceled'; 6 | const COMPOSE_TIMEOUT = 'Metasync: asynchronous composition timed out'; 7 | 8 | // Asynchronous functions composition 9 | // Array of functions results in sequential execution: `[f1, f2, f3]` 10 | // Double brackets array of functions results 11 | // in parallel execution: `[[f1, f2, f3]]` 12 | // flow - , callback-last / err-first 13 | // 14 | // Returns: , composed callback-last / err-first 15 | // 16 | // Example: 17 | // const composed = metasync([f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]); 18 | const compose = (flow) => { 19 | const comp = (data, callback) => { 20 | if (!callback) { 21 | if (typeof data === 'function') { 22 | callback = data; 23 | data = {}; 24 | } else { 25 | comp.data = data; 26 | return comp; 27 | } 28 | } 29 | comp.done = callback; 30 | if (comp.canceled) { 31 | if (callback) { 32 | callback(new Error(COMPOSE_CANCELED)); 33 | } 34 | return comp; 35 | } 36 | if (comp.timeout) { 37 | comp.timer = setTimeout(() => { 38 | comp.timer = null; 39 | if (callback) { 40 | callback(new Error(COMPOSE_TIMEOUT)); 41 | comp.done = null; 42 | } 43 | }, comp.timeout); 44 | } 45 | comp.context = data; 46 | comp.arrayed = Array.isArray(comp.context); 47 | comp.paused = false; 48 | if (comp.len === 0) { 49 | comp.finalize(); 50 | return comp; 51 | } 52 | if (comp.parallelize) comp.parallel(); 53 | else comp.sequential(); 54 | return comp; 55 | }; 56 | const first = flow[0]; 57 | const parallelize = flow.length === 1 && Array.isArray(first); 58 | const fns = parallelize ? first : flow; 59 | comp.fns = fns; 60 | comp.parallelize = parallelize; 61 | comp.context = null; 62 | comp.timeout = 0; 63 | comp.timer = null; 64 | comp.len = fns.length; 65 | comp.canceled = false; 66 | comp.paused = true; 67 | comp.arrayed = false; 68 | comp.done = null; 69 | comp.onResume = null; 70 | Object.setPrototypeOf(comp, Composition.prototype); 71 | return comp; 72 | }; 73 | 74 | Composition.prototype.on = function (name, callback) { 75 | if (name === 'resume') { 76 | this.onResume = callback; 77 | } 78 | }; 79 | 80 | Composition.prototype.finalize = function (err) { 81 | if (this.canceled) return; 82 | if (this.timer) { 83 | clearTimeout(this.timer); 84 | this.timer = null; 85 | } 86 | const callback = this.done; 87 | if (callback) { 88 | if (this.paused) { 89 | this.on('resume', () => { 90 | this.done = null; 91 | callback(err, this.context); 92 | }); 93 | } else { 94 | this.done = null; 95 | callback(err, this.context); 96 | } 97 | } 98 | }; 99 | 100 | Composition.prototype.collect = function (err, result) { 101 | if (this.canceled) return; 102 | if (err) { 103 | const callback = this.done; 104 | if (callback) { 105 | this.done = null; 106 | callback(err); 107 | } 108 | return; 109 | } 110 | if (result !== this.context && result !== undefined) { 111 | if (this.arrayed) { 112 | this.context.push(result); 113 | } else if (typeof result === 'object') { 114 | Object.assign(this.context, result); 115 | } 116 | } 117 | }; 118 | 119 | Composition.prototype.parallel = function () { 120 | let counter = 0; 121 | const next = (err, result) => { 122 | this.collect(err, result); 123 | if (++counter === this.len) this.finalize(); 124 | }; 125 | const fns = this.fns; 126 | const len = this.len; 127 | const context = this.context; 128 | for (let i = 0; i < len; i++) { 129 | const fn = fns[i]; 130 | const fc = Array.isArray(fn) ? compose(fn) : fn; 131 | fc(context, next); 132 | } 133 | }; 134 | 135 | Composition.prototype.sequential = function () { 136 | let counter = -1; 137 | const fns = this.fns; 138 | const len = this.len; 139 | const context = this.context; 140 | const next = (err, result) => { 141 | if (this.canceled) return; 142 | if (err || result) this.collect(err, result); 143 | if (++counter === len) return void this.finalize(); 144 | const fn = fns[counter]; 145 | const fc = Array.isArray(fn) ? compose(fn) : fn; 146 | if (this.paused) { 147 | this.on('resume', () => fc(context, next)); 148 | } else { 149 | fc(context, next); 150 | } 151 | }; 152 | next(); 153 | }; 154 | 155 | Composition.prototype.then = function (fulfill, reject) { 156 | if (this.canceled) { 157 | reject(new Error(COMPOSE_CANCELED)); 158 | return this; 159 | } 160 | this((err, result) => { 161 | if (err) reject(err); 162 | else fulfill(result); 163 | }); 164 | return this; 165 | }; 166 | 167 | // Clone composed 168 | Composition.prototype.clone = function () { 169 | const fns = this.fns.slice(); 170 | const flow = this.parallelize ? [fns] : fns; 171 | return compose(flow); 172 | }; 173 | 174 | // Pause execution 175 | Composition.prototype.pause = function () { 176 | if (this.canceled) return this; 177 | this.paused = true; 178 | return this; 179 | }; 180 | 181 | // Resume execution 182 | Composition.prototype.resume = function () { 183 | if (this.canceled) return this; 184 | this.paused = false; 185 | if (this.onResume) { 186 | const callback = this.onResume; 187 | this.onResume = null; 188 | callback(); 189 | } 190 | return this; 191 | }; 192 | 193 | // Set timeout 194 | // msec - 195 | Composition.prototype.timeout = function (msec) { 196 | this.timeout = msec; 197 | return this; 198 | }; 199 | 200 | // Cancel execution where possible 201 | Composition.prototype.cancel = function () { 202 | if (this.canceled) return this; 203 | this.canceled = true; 204 | const callback = this.done; 205 | if (callback) { 206 | this.done = null; 207 | callback(new Error(COMPOSE_CANCELED)); 208 | } 209 | return this; 210 | }; 211 | 212 | module.exports = { compose, Composition }; 213 | -------------------------------------------------------------------------------- /lib/control.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('@metarhia/common'); 4 | 5 | const { each } = require('./array'); 6 | 7 | // Executes all asynchronous functions and pass first result to callback 8 | // fns - , callback-last / err-first 9 | // callback - , on done, err-first 10 | const firstOf = (fns, callback) => { 11 | const done = common.once(callback); 12 | each(fns, (f, iterCb) => 13 | f((...args) => { 14 | done(...args); 15 | iterCb(...args); 16 | }), 17 | ); 18 | }; 19 | 20 | // Parallel execution 21 | // Signature: fns[, context], callback 22 | // fns - , callback-last / err-first 23 | // context - , incoming data, optional 24 | // callback - , on done, err-first 25 | // 26 | // Example: 27 | // metasync.parallel([f1, f2, f3], (err, data) => {}); 28 | const parallel = (fns, context, callback) => { 29 | if (!callback) { 30 | callback = context; 31 | context = {}; 32 | } 33 | const done = common.once(callback); 34 | const isArray = Array.isArray(context); 35 | const len = fns.length; 36 | if (len === 0) return void done(null, context); 37 | let counter = 0; 38 | 39 | const finishFn = (fn, err, result) => { 40 | if (err) return void done(err); 41 | if (result !== context && result !== undefined) { 42 | if (isArray) context.push(result); 43 | else if (typeof result === 'object') Object.assign(context, result); 44 | } 45 | if (++counter === len) done(null, context); 46 | }; 47 | 48 | // fn may be array of function 49 | for (const fn of fns) { 50 | const finish = finishFn.bind(null, fn); 51 | if (fn.length === 2) fn(context, finish); 52 | else fn(finish); 53 | } 54 | }; 55 | 56 | // Sequential execution 57 | // Signature: fns[, context], callback 58 | // fns - , callback-last with err-first contract 59 | // context - , incoming data, optional 60 | // callback - , err-first on done 61 | // 62 | // Example: 63 | // metasync.sequential([f1, f2, f3], (err, data) => {}); 64 | const sequential = (fns, context, callback) => { 65 | if (!callback) { 66 | callback = context; 67 | context = {}; 68 | } 69 | const done = common.once(callback); 70 | const isArray = Array.isArray(context); 71 | const len = fns.length; 72 | if (len === 0) return void done(null, context); 73 | let i = -1; 74 | 75 | const next = () => { 76 | let fn = null; 77 | const finish = (err, result) => { 78 | if (result !== context && result !== undefined) { 79 | if (isArray) context.push(result); 80 | else if (typeof result === 'object') Object.assign(context, result); 81 | } 82 | if (err) return void done(err); 83 | next(); 84 | }; 85 | if (++i === len) return void done(null, context); 86 | fn = fns[i]; 87 | if (fn.length === 2) fn(context, finish); 88 | else fn(finish); 89 | }; 90 | 91 | next(); 92 | }; 93 | 94 | // Run `asyncFn` if `condition` is truthy, else return `defaultVal` to callback. 95 | // Signature: condition[, defaultVal], asyncFn, ...args 96 | // condition - 97 | // defaultVal - , optional, value that will be returned to callback if 98 | // `condition` is falsy. 99 | // asyncFn - , callback-last function that will be executed if 100 | // `condition` if truthy 101 | // args - , args to pass to `asyncFn` 102 | const runIf = (condition, defaultVal, asyncFn, ...args) => { 103 | if (typeof defaultVal === 'function') { 104 | args.unshift(asyncFn); 105 | asyncFn = defaultVal; 106 | defaultVal = undefined; 107 | } 108 | if (condition) { 109 | asyncFn(...args); 110 | } else { 111 | const callback = common.last(args); 112 | process.nextTick(callback, null, defaultVal); 113 | } 114 | }; 115 | 116 | // Run `asyncFn` if it is provided 117 | // Signature: asyncFn, ...args 118 | // asyncFn - , callback-last function that will be executed if it 119 | // is provided 120 | // args - , args to pass to `asyncFn` 121 | const runIfFn = (asyncFn, ...args) => { 122 | runIf(asyncFn, undefined, asyncFn, ...args); 123 | }; 124 | 125 | module.exports = { 126 | firstOf, 127 | parallel, 128 | sequential, 129 | runIf, 130 | runIfFn, 131 | }; 132 | -------------------------------------------------------------------------------- /lib/do.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Do() {} 4 | 5 | const chain = function (fn, ...args) { 6 | const current = (done) => { 7 | if (done) current.done = done; 8 | if (current.prev) { 9 | current.prev.next = current; 10 | current.prev(); 11 | } else { 12 | current.forward(); 13 | } 14 | return current; 15 | }; 16 | 17 | const prev = this instanceof Do ? this : null; 18 | const fields = { prev, fn, args, done: null }; 19 | 20 | Object.setPrototypeOf(current, Do.prototype); 21 | return Object.assign(current, fields); 22 | }; 23 | 24 | Do.prototype.do = function (fn, ...args) { 25 | return chain.call(this, fn, ...args); 26 | }; 27 | 28 | Do.prototype.forward = function () { 29 | if (this.fn) { 30 | this.fn(...this.args, (err, data) => { 31 | const next = this.next; 32 | if (next) { 33 | if (next.fn) next.forward(); 34 | } else if (this.done) { 35 | this.done(err, data); 36 | } 37 | }); 38 | } 39 | }; 40 | 41 | module.exports = { do: chain }; 42 | -------------------------------------------------------------------------------- /lib/fp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let asyncChainMethods = null; 4 | // Convert synchronous function to asynchronous 5 | // Transform function with args arguments and callback 6 | // to function with args as separate values and callback 7 | // fn - , callback-last / err-first 8 | // 9 | // Returns: 10 | const toAsync = 11 | (fn) => 12 | (...argsCb) => { 13 | const len = argsCb.length - 1; 14 | const callback = argsCb[len]; 15 | const args = argsCb.slice(0, len); 16 | return fn(args, callback); 17 | }; 18 | 19 | // Wrap function adding async chain methods 20 | // fn - , asynchronous 21 | // args - , its arguments 22 | const asAsync = (fn, ...args) => { 23 | const wrapped = fn.bind(null, ...args); 24 | for (const name in asyncChainMethods) { 25 | const method = asyncChainMethods[name]; 26 | wrapped[name] = (...args) => asAsync(method(wrapped, ...args)); 27 | } 28 | return wrapped; 29 | }; 30 | 31 | // Applicative f => a -> f a 32 | // args - 33 | const of = (...args) => asAsync((callback) => callback(null, ...args)); 34 | 35 | // Monoid m => a -> a -> a 36 | // fn1 - 37 | // fn2 - 38 | const concat = (fn1, fn2) => 39 | toAsync((args1, callback) => 40 | fn1(...args1, (err, ...args2) => { 41 | if (err !== null) callback(err); 42 | else fn2(...args2, callback); 43 | }), 44 | ); 45 | 46 | // Functor f => (a -> b) -> f a -> f b 47 | // fn1 - 48 | // f - 49 | const fmap = (fn1, f) => { 50 | const fn2 = toAsync((args, callback) => of(f(...args))(callback)); 51 | return concat(fn1, fn2); 52 | }; 53 | 54 | // Applicative f => f (a -> b) -> f a -> f b 55 | // fn - 56 | // funcA - 57 | const ap = (fn, funcA) => concat(funcA, (f, callback) => fmap(fn, f)(callback)); 58 | 59 | asyncChainMethods = { fmap, ap, concat }; 60 | 61 | module.exports = { 62 | toAsync, 63 | asAsync, 64 | of, 65 | concat, 66 | fmap, 67 | ap, 68 | }; 69 | -------------------------------------------------------------------------------- /lib/memoize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Memoized() {} 4 | 5 | // Create memoized function 6 | // fn - , sync or async 7 | // 8 | // Returns: , memoized 9 | const memoize = (fn) => { 10 | const cache = new Map(); 11 | 12 | const memoized = function (...args) { 13 | const callback = args.pop(); 14 | const key = args[0]; 15 | const record = cache.get(key); 16 | if (record) return void callback(record.err, record.data); 17 | fn(...args, (err, data) => { 18 | memoized.add(key, err, data); 19 | memoized.emit('memoize', key, err, data); 20 | callback(err, data); 21 | }); 22 | }; 23 | 24 | const fields = { 25 | cache, 26 | timeout: 0, 27 | limit: 0, 28 | size: 0, 29 | maxSize: 0, 30 | maxCount: 0, 31 | events: { 32 | timeout: null, 33 | memoize: null, 34 | overflow: null, 35 | add: null, 36 | del: null, 37 | clear: null, 38 | }, 39 | }; 40 | 41 | Object.setPrototypeOf(memoized, Memoized.prototype); 42 | return Object.assign(memoized, fields); 43 | }; 44 | 45 | Memoized.prototype.clear = function () { 46 | this.emit('clear'); 47 | this.cache.clear(); 48 | }; 49 | 50 | Memoized.prototype.add = function (key, err, data) { 51 | this.emit('add', err, data); 52 | this.cache.set(key, { err, data }); 53 | return this; 54 | }; 55 | 56 | Memoized.prototype.del = function (key) { 57 | this.emit('del', key); 58 | this.cache.delete(key); 59 | return this; 60 | }; 61 | 62 | Memoized.prototype.get = function (key, callback) { 63 | const record = this.cache.get(key); 64 | callback(record.err, record.data); 65 | return this; 66 | }; 67 | 68 | // Add event listener 69 | // eventName - 70 | // listener - , handler 71 | // 72 | // Example: 73 | // const memoized = new Memoized(); 74 | // memoized.on('memoize', (err, data) => { ... }); 75 | // memoized.on('add', (key, err, data) => { ... }); 76 | // memoized.on('del', (key) => { ... }) 77 | // memoized.on('clear', () => { ... }) 78 | Memoized.prototype.on = function (eventName, listener) { 79 | if (eventName in this.events) { 80 | this.events[eventName] = listener; 81 | } 82 | }; 83 | 84 | // Emit Memoized events 85 | // eventName - 86 | // args - , rest arguments 87 | Memoized.prototype.emit = function (eventName, ...args) { 88 | const event = this.events[eventName]; 89 | if (event) event(...args); 90 | }; 91 | 92 | module.exports = { memoize, Memoized }; 93 | -------------------------------------------------------------------------------- /lib/poolify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const duplicate = (factory, n) => Array.from({ length: n }, factory); 4 | 5 | const provide = (callback) => (item) => { 6 | setImmediate(() => { 7 | callback(item); 8 | }); 9 | }; 10 | 11 | const poolify = (factory, min, norm, max) => { 12 | let allocated = norm; 13 | const pool = (par) => { 14 | if (Array.isArray(par)) { 15 | while (par.length) { 16 | const item = par.shift(); 17 | const delayed = pool.delayed.shift(); 18 | if (delayed) delayed(item); 19 | else pool.items.push(item); 20 | } 21 | return pool; 22 | } 23 | if (pool.items.length < min && allocated < max) { 24 | const grow = Math.min(max - allocated, norm - pool.items.length); 25 | allocated += grow; 26 | const items = duplicate(factory, grow); 27 | pool.items.push(...items); 28 | } 29 | const res = pool.items.pop(); 30 | if (!par) return res; 31 | const callback = provide(par); 32 | if (res) callback(res); 33 | else pool.delayed.push(callback); 34 | return pool; 35 | }; 36 | return Object.assign(pool, { 37 | items: duplicate(factory, norm), 38 | delayed: [], 39 | }); 40 | }; 41 | 42 | module.exports = { poolify }; 43 | -------------------------------------------------------------------------------- /lib/poolify.opt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const duplicate = (factory, n) => Array.from({ length: n }, factory); 4 | 5 | const provide = (callback) => (item) => { 6 | setImmediate(() => { 7 | callback(item); 8 | }); 9 | }; 10 | 11 | const poolify = (factory, min, norm, max) => { 12 | let allocated = norm; 13 | const items = duplicate(factory, norm); 14 | const delayed = []; 15 | const pool = (par) => { 16 | if (Array.isArray(par)) { 17 | while (par.length) { 18 | const item = par.shift(); 19 | const request = delayed.shift(); 20 | if (request) request(item); 21 | else items.push(item); 22 | } 23 | return pool; 24 | } 25 | if (items.length < min && allocated < max) { 26 | const grow = Math.min(max - allocated, norm - items.length); 27 | allocated += grow; 28 | const instances = duplicate(factory, grow); 29 | items.push(...instances); 30 | } 31 | const res = items.pop(); 32 | if (!par) return res; 33 | const callback = provide(par); 34 | if (res) callback(res); 35 | else delayed.push(callback); 36 | return pool; 37 | }; 38 | return pool; 39 | }; 40 | 41 | module.exports = { poolify }; 42 | -------------------------------------------------------------------------------- /lib/poolify.symbol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const poolified = Symbol('poolified'); 4 | 5 | const mixFlag = { [poolified]: true }; 6 | 7 | const duplicate = (factory, n) => 8 | Array.from({ length: n }, factory).map((instance) => 9 | Object.assign(instance, mixFlag), 10 | ); 11 | 12 | const provide = (callback) => (item) => { 13 | setImmediate(() => { 14 | callback(item); 15 | }); 16 | }; 17 | 18 | const poolify = (factory, min, norm, max) => { 19 | let allocated = norm; 20 | const pool = (par) => { 21 | if (par && par[poolified]) { 22 | const delayed = pool.delayed.shift(); 23 | if (delayed) delayed(par); 24 | else pool.items.push(par); 25 | return pool; 26 | } 27 | if (pool.items.length < min && allocated < max) { 28 | const grow = Math.min(max - allocated, norm - pool.items.length); 29 | allocated += grow; 30 | const items = duplicate(factory, grow); 31 | pool.items.push(...items); 32 | } 33 | const res = pool.items.pop(); 34 | if (!par) return res; 35 | const callback = provide(par); 36 | if (res) callback(res); 37 | else pool.delayed.push(callback); 38 | return pool; 39 | }; 40 | return Object.assign(pool, { 41 | items: duplicate(factory, norm), 42 | delayed: [], 43 | }); 44 | }; 45 | 46 | module.exports = { poolify }; 47 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Queue constructor 4 | // concurrency - , asynchronous concurrency 5 | function Queue(concurrency) { 6 | this.paused = false; 7 | this.concurrency = concurrency; 8 | this.waitTimeout = 0; 9 | this.processTimeout = 0; 10 | this.throttleCount = 0; 11 | this.throttleInterval = 1000; 12 | this.count = 0; 13 | this.tasks = []; 14 | this.waiting = []; 15 | this.factors = {}; 16 | this.fifoMode = true; 17 | this.roundRobinMode = false; 18 | this.priorityMode = false; 19 | this.onProcess = null; 20 | this.onDone = null; 21 | this.onSuccess = null; 22 | this.onTimeout = null; 23 | this.onFailure = null; 24 | this.onDrain = null; 25 | } 26 | 27 | const QUEUE_TIMEOUT = 'Metasync: Queue timed out'; 28 | 29 | // Set wait before processing timeout 30 | // msec - , wait timeout for single item 31 | // 32 | // Returns: 33 | Queue.prototype.wait = function (msec) { 34 | this.waitTimeout = msec; 35 | return this; 36 | }; 37 | 38 | // Throttle to limit throughput 39 | // Signature: count[, interval] 40 | // count - , item count 41 | // interval - , per interval, optional 42 | // default: 1000 msec 43 | // 44 | // Returns: 45 | Queue.prototype.throttle = function (count, interval = 1000) { 46 | this.throttleCount = count; 47 | this.throttleInterval = interval; 48 | return this; 49 | }; 50 | 51 | // Add item to queue 52 | // Signature: item[, factor[, priority]] 53 | // item - , to be added 54 | // factor - | , type, source, 55 | // destination or path, optional 56 | // priority - , optional 57 | // 58 | // Returns: 59 | Queue.prototype.add = function (item, factor = 0, priority = 0) { 60 | if (this.priorityMode && !this.roundRobinMode) { 61 | priority = factor; 62 | factor = 0; 63 | } 64 | const task = [item, factor, priority]; 65 | const slot = this.count < this.concurrency; 66 | if (!this.paused && slot && this.onProcess) { 67 | this.next(task); 68 | return this; 69 | } 70 | let tasks; 71 | if (this.roundRobinMode) { 72 | tasks = this.factors[factor]; 73 | if (!tasks) { 74 | tasks = []; 75 | this.factors[factor] = tasks; 76 | this.waiting.push(tasks); 77 | } 78 | } else { 79 | tasks = this.tasks; 80 | } 81 | 82 | if (this.fifoMode) tasks.push(task); 83 | else tasks.unshift(task); 84 | 85 | if (this.priorityMode) { 86 | if (this.fifoMode) { 87 | tasks.sort((a, b) => b[2] - a[2]); 88 | } else { 89 | tasks.sort((a, b) => a[2] - b[2]); 90 | } 91 | } 92 | return this; 93 | }; 94 | 95 | // Process next item 96 | // task - , next task [item, factor, priority] 97 | // 98 | // Returns: 99 | Queue.prototype.next = function (task) { 100 | const item = task[0]; 101 | let timer; 102 | this.count++; 103 | if (this.processTimeout) { 104 | timer = setTimeout(() => { 105 | const err = new Error(QUEUE_TIMEOUT); 106 | if (this.onTimeout) this.onTimeout(err); 107 | }, this.processTimeout); 108 | } 109 | this.onProcess(item, (err, result) => { 110 | if (this.onDone) this.onDone(err, result); 111 | if (err) { 112 | if (this.onFailure) this.onFailure(err); 113 | } else if (this.onSuccess) { 114 | this.onSuccess(result); 115 | } 116 | if (timer) { 117 | clearTimeout(timer); 118 | timer = null; 119 | } 120 | this.count--; 121 | if (this.tasks.length > 0 || this.waiting.length > 0) { 122 | this.takeNext(); 123 | } else if (this.count === 0 && this.onDrain) { 124 | this.onDrain(); 125 | } 126 | }); 127 | return this; 128 | }; 129 | 130 | // Prepare next item for processing 131 | // 132 | // Returns: 133 | Queue.prototype.takeNext = function () { 134 | if (this.paused || !this.onProcess) { 135 | return this; 136 | } 137 | let tasks; 138 | if (this.roundRobinMode) { 139 | tasks = this.waiting.shift(); 140 | if (tasks.length > 1) { 141 | this.waiting.push(tasks); 142 | } 143 | } else { 144 | tasks = this.tasks; 145 | } 146 | const task = tasks.shift(); 147 | if (task) this.next(task); 148 | return this; 149 | }; 150 | 151 | // Pause queue 152 | // This function is not completely implemented yet 153 | // 154 | // Returns: 155 | Queue.prototype.pause = function () { 156 | this.paused = true; 157 | return this; 158 | }; 159 | 160 | // Resume queue 161 | // This function is not completely implemented yet 162 | // 163 | // Returns: 164 | Queue.prototype.resume = function () { 165 | this.paused = false; 166 | return this; 167 | }; 168 | 169 | // Clear queue 170 | // 171 | // Returns: 172 | Queue.prototype.clear = function () { 173 | this.count = 0; 174 | this.tasks = []; 175 | this.waiting = []; 176 | this.factors = {}; 177 | return this; 178 | }; 179 | 180 | // Set timeout interval and listener 181 | // msec - , process timeout for single item 182 | // onTimeout - 183 | // 184 | // Returns: 185 | Queue.prototype.timeout = function (msec, onTimeout = null) { 186 | this.processTimeout = msec; 187 | if (onTimeout) this.onTimeout = onTimeout; 188 | return this; 189 | }; 190 | 191 | // Set processing function 192 | // fn - 193 | // item - 194 | // callback - 195 | // err - | 196 | // result - 197 | // 198 | // Returns: 199 | Queue.prototype.process = function (fn) { 200 | this.onProcess = fn; 201 | return this; 202 | }; 203 | 204 | // Set listener on processing done 205 | // fn - , done listener 206 | // err - | 207 | // result - 208 | // 209 | // Returns: 210 | Queue.prototype.done = function (fn) { 211 | this.onDone = fn; 212 | return this; 213 | }; 214 | 215 | // Set listener on processing success 216 | // listener - , on success 217 | // item - 218 | // 219 | // Returns: 220 | Queue.prototype.success = function (listener) { 221 | this.onSuccess = listener; 222 | return this; 223 | }; 224 | 225 | // Set listener on processing error 226 | // listener - , on failure 227 | // err - | 228 | // 229 | // Returns: 230 | Queue.prototype.failure = function (listener) { 231 | this.onFailure = listener; 232 | return this; 233 | }; 234 | 235 | // Set listener on drain Queue 236 | // listener - , on drain 237 | // 238 | // Returns: 239 | Queue.prototype.drain = function (listener) { 240 | this.onDrain = listener; 241 | return this; 242 | }; 243 | 244 | // Switch to FIFO mode (default for Queue) 245 | // 246 | // Returns: 247 | Queue.prototype.fifo = function () { 248 | this.fifoMode = true; 249 | return this; 250 | }; 251 | 252 | // Switch to LIFO mode 253 | // 254 | // Returns: 255 | Queue.prototype.lifo = function () { 256 | this.fifoMode = false; 257 | return this; 258 | }; 259 | 260 | // Activate or deactivate priority mode 261 | // flag - , default: true, false will 262 | // disable priority mode 263 | // 264 | // Returns: 265 | Queue.prototype.priority = function (flag = true) { 266 | this.priorityMode = flag; 267 | return this; 268 | }; 269 | 270 | // Activate or deactivate round robin mode 271 | // flag - , default: true, false will 272 | // disable roundRobin mode 273 | // 274 | // Returns: 275 | Queue.prototype.roundRobin = function (flag = true) { 276 | this.roundRobinMode = flag; 277 | return this; 278 | }; 279 | 280 | // Pipe processed items to different queue 281 | // dest - , destination queue 282 | // 283 | // Returns: 284 | Queue.prototype.pipe = function (dest) { 285 | if (dest instanceof Queue) { 286 | this.success((item) => { 287 | dest.add(item); 288 | }); 289 | } 290 | return this; 291 | }; 292 | 293 | // Create Queue instance 294 | // concurrency - , simultaneous and 295 | // asynchronously executing tasks 296 | // 297 | // Returns: 298 | const queue = (concurrency) => new Queue(concurrency); 299 | 300 | module.exports = { queue, Queue }; 301 | -------------------------------------------------------------------------------- /lib/throttle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Get throttling function, executed once per interval 4 | // Signature: timeout, fn, ...args 5 | // timeout - , msec interval 6 | // fn - , to be throttled 7 | // args - , arguments for fn, optional 8 | // 9 | // Returns: 10 | const throttle = (timeout, fn, ...args) => { 11 | let timer; 12 | let wait = false; 13 | 14 | const execute = args 15 | ? (...pars) => (pars ? fn(...args, ...pars) : fn(...args)) 16 | : (...pars) => (pars ? fn(...pars) : fn()); 17 | 18 | const delayed = (...pars) => { 19 | timer = undefined; 20 | if (wait) execute(...pars); 21 | }; 22 | 23 | const throttled = (...pars) => { 24 | if (!timer) { 25 | timer = setTimeout(delayed, timeout, ...pars); 26 | wait = false; 27 | execute(...pars); 28 | } 29 | wait = true; 30 | }; 31 | 32 | return throttled; 33 | }; 34 | 35 | // Debounce function, delayed execution 36 | // Signature: timeout, fn, ...args 37 | // timeout - , msec 38 | // fn - , to be debounced 39 | // args - , arguments for fn, optional 40 | const debounce = (timeout, fn, ...args) => { 41 | let timer; 42 | 43 | const debounced = () => (args ? fn(...args) : fn()); 44 | 45 | const wrapped = () => { 46 | if (timer) clearTimeout(timer); 47 | timer = setTimeout(debounced, timeout); 48 | }; 49 | 50 | return wrapped; 51 | }; 52 | 53 | const FN_TIMEOUT = 'Metasync: asynchronous function timed out'; 54 | 55 | // Set timeout for asynchronous function execution 56 | // timeout - , time interval 57 | // fn - , to be executed 58 | // callback - , callback(...args), on done 59 | // args - 60 | const timeout = (timeout, fn, callback) => { 61 | let finished = false; 62 | 63 | const timer = setTimeout(() => { 64 | finished = true; 65 | callback(new Error(FN_TIMEOUT)); 66 | }, timeout); 67 | 68 | fn((...args) => { 69 | if (!finished) { 70 | clearTimeout(timer); 71 | finished = true; 72 | callback(...args); 73 | } 74 | }); 75 | }; 76 | 77 | module.exports = { 78 | throttle, 79 | debounce, 80 | timeout, 81 | }; 82 | -------------------------------------------------------------------------------- /metasync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('@metarhia/common'); 4 | const nodeVerion = common.between(process.version, 'v', '.'); 5 | 6 | const submodules = [ 7 | 'composition', // Unified abstraction 8 | 'adapters', // Adapters to convert different async contracts 9 | 'array', // Array utilities 10 | 'collector', // DataCollector and KeyCollector 11 | 'control', // Control flow utilities 12 | 'do', // Simple chain/do 13 | 'fp', // Async utils for functional programming 14 | 'memoize', // Async memoization 15 | 'poolify', // Create pool from factory 16 | 'queue', // Concurrent queue 17 | 'throttle', // Throttling utilities 18 | ].map((path) => require('./lib/' + path)); 19 | 20 | if (nodeVerion >= 10) { 21 | submodules.push(require('./lib/async-iterator')); 22 | } 23 | 24 | const { compose } = submodules[0]; 25 | module.exports = Object.assign(compose, ...submodules); 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metasync", 3 | "version": "0.3.33", 4 | "author": "Timur Shemsedinov ", 5 | "description": "Asynchronous Programming Library", 6 | "license": "MIT", 7 | "keywords": [ 8 | "metasync", 9 | "callback", 10 | "promise", 11 | "async", 12 | "asyncronous", 13 | "parallel", 14 | "sequential", 15 | "metarhia", 16 | "flow", 17 | "collector", 18 | "errback", 19 | "err-first", 20 | "error-first", 21 | "callback-last", 22 | "throttle", 23 | "impress", 24 | "datacollector", 25 | "keycollector", 26 | "composition" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/metarhia/metasync" 31 | }, 32 | "main": "metasync.js", 33 | "browser": { 34 | "metasync.js": "dist/metasync.js" 35 | }, 36 | "files": [ 37 | "dist/", 38 | "lib/" 39 | ], 40 | "readmeFilename": "README.md", 41 | "scripts": { 42 | "test": "npm run lint && metatests test/ && metatests tests/async-iterator.js", 43 | "perf": "tests/load/run.sh", 44 | "lint": "eslint . && prettier -c \"**/*.js\" \"**/*.json\" \"**/*.md\" \".*rc\"", 45 | "doc": "metadoc", 46 | "fmt": "prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \".*rc\"", 47 | "build": "babel metasync.js -d dist && babel lib -d dist/lib", 48 | "prepublish": "npm run -s build" 49 | }, 50 | "engines": { 51 | "node": ">=14.0.0" 52 | }, 53 | "dependencies": { 54 | "@metarhia/common": "^2.2.1" 55 | }, 56 | "devDependencies": { 57 | "@babel/cli": "^7.22.9", 58 | "@babel/core": "^7.22.9", 59 | "@babel/preset-env": "^7.22.9", 60 | "@metarhia/doc": "^0.7.0", 61 | "eslint": "^8.44.0", 62 | "eslint-config-metarhia": "^8.2.0", 63 | "eslint-config-prettier": "^8.8.0", 64 | "eslint-plugin-import": "^2.27.5", 65 | "eslint-plugin-prettier": "^5.0.0-alpha.2", 66 | "metatests": "^0.8.2", 67 | "prettier": "^3.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/adapters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('callbackify: Promise to callback-last', (test) => { 7 | const promiseReturning = () => Promise.resolve('result'); 8 | const asyncFn = metasync.callbackify(promiseReturning); 9 | 10 | asyncFn((err, value) => { 11 | if (err) { 12 | test.error(err, 'must not throw'); 13 | } 14 | test.strictSame(value, 'result'); 15 | test.end(); 16 | }); 17 | }); 18 | 19 | metatests.test('asyncify: sync function to callback-last', (test) => { 20 | const fn = (par) => par; 21 | const asyncFn = metasync.asyncify(fn); 22 | 23 | asyncFn('result', (err, value) => { 24 | if (err) { 25 | test.error(err, 'must not throw'); 26 | } 27 | test.strictSame(value, 'result'); 28 | test.end(); 29 | }); 30 | }); 31 | 32 | metatests.test('promisify: callback-last to Promise', (test) => { 33 | const id = 100; 34 | const data = { key: 'value' }; 35 | 36 | const getDataAsync = (dataId, callback) => { 37 | test.strictSame(dataId, id); 38 | callback(null, data); 39 | }; 40 | 41 | const getDataPromise = metasync.promisify(getDataAsync); 42 | getDataPromise(id) 43 | .then((result) => { 44 | test.strictSame(result, data); 45 | test.end(); 46 | }) 47 | .catch((err) => { 48 | test.error(err, 'must not throw'); 49 | }); 50 | }); 51 | 52 | metatests.test('promisify: callback-last to Promise throw', (test) => { 53 | const id = 100; 54 | 55 | const getDataAsync = (dataId, callback) => { 56 | test.strictSame(dataId, id); 57 | callback(new Error('Data not found')); 58 | }; 59 | 60 | const getDataPromise = metasync.promisify(getDataAsync); 61 | getDataPromise(id) 62 | .then((result) => { 63 | test.notOk(result); 64 | }) 65 | .catch((err) => { 66 | test.ok(err); 67 | test.end(); 68 | }); 69 | }); 70 | 71 | metatests.test('promisify: sync function to Promise', (test) => { 72 | const id = 100; 73 | const data = { key: 'value' }; 74 | 75 | const getDataSync = (dataId) => { 76 | test.strictSame(dataId, id); 77 | return data; 78 | }; 79 | 80 | const getDataPromise = metasync.promisifySync(getDataSync); 81 | getDataPromise(id) 82 | .then((result) => { 83 | test.strictSame(result, data); 84 | test.end(); 85 | }) 86 | .catch((err) => { 87 | test.error(err, 'must not throw'); 88 | }); 89 | }); 90 | 91 | metatests.test('promisify: sync to Promise throw', (test) => { 92 | const id = 100; 93 | 94 | const getDataSync = (dataId) => { 95 | test.strictSame(dataId, id); 96 | throw new Error('Data not found'); 97 | }; 98 | 99 | const getDataPromise = metasync.promisifySync(getDataSync); 100 | getDataPromise(id) 101 | .then((result) => { 102 | test.notOk(result); 103 | }) 104 | .catch((err) => { 105 | test.ok(err); 106 | test.end(); 107 | }); 108 | }); 109 | 110 | metatests.test('promiseToCallbackLast: Promise to callback-last', (test) => { 111 | const promise = Promise.resolve('result'); 112 | const asyncFn = metasync.promiseToCallbackLast(promise); 113 | 114 | asyncFn((err, value) => { 115 | if (err) { 116 | test.error(err, 'must not throw'); 117 | } 118 | test.strictSame(value, 'result'); 119 | test.end(); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/array.asyncMap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('succesfull map', (test) => { 7 | test.plan(2); 8 | 9 | const arr = [1, 2, 3]; 10 | const expectedArr = [2, 4, 6]; 11 | 12 | metasync.asyncMap( 13 | arr, 14 | (item) => item * 2, 15 | (err, newArr) => { 16 | test.error(err); 17 | test.strictSame(newArr, expectedArr); 18 | }, 19 | ); 20 | }); 21 | 22 | const doSmth = (time) => { 23 | const begin = Date.now(); 24 | while (Date.now() - begin < time); 25 | }; 26 | 27 | metatests.test('Non-blocking', (test) => { 28 | const ITEM_TIME = 1; 29 | const TIMER_TIME = 9; 30 | const ARRAY_SIZE = 1000; 31 | const EXPECTED_PERCENT = 0.5; 32 | const EXPECTED_DEVIATION = 0.4; 33 | 34 | const arr = new Array(ARRAY_SIZE).fill(1); 35 | 36 | const timer = setInterval(() => doSmth(TIMER_TIME), 1); 37 | 38 | const begin = Date.now(); 39 | metasync.asyncMap( 40 | arr, 41 | () => doSmth(ITEM_TIME), 42 | { percent: EXPECTED_PERCENT }, 43 | () => { 44 | clearInterval(timer); 45 | 46 | const mapTime = ITEM_TIME * ARRAY_SIZE; 47 | const allTime = Date.now() - begin; 48 | const actualPercent = mapTime / allTime; 49 | const actualDeviation = Math.abs(actualPercent - EXPECTED_PERCENT); 50 | test.assert(actualDeviation <= EXPECTED_DEVIATION); 51 | test.end(); 52 | }, 53 | ); 54 | }); 55 | -------------------------------------------------------------------------------- /test/array.each.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('successful each', (test) => { 7 | const arr = [1, 2, 3, 4]; 8 | 9 | const elementsSet = new Set(); 10 | const expectedElementsSet = new Set(arr); 11 | 12 | metasync.each( 13 | arr, 14 | (el, callback) => 15 | process.nextTick(() => { 16 | elementsSet.add(el); 17 | callback(null); 18 | }), 19 | (err) => { 20 | test.error(err); 21 | test.strictSame(elementsSet, expectedElementsSet); 22 | test.end(); 23 | }, 24 | ); 25 | }); 26 | 27 | metatests.test('each with empty array', (test) => { 28 | const arr = []; 29 | 30 | const elementsSet = new Set(); 31 | const expectedElementsSet = new Set(arr); 32 | 33 | metasync.each( 34 | arr, 35 | (el, callback) => 36 | process.nextTick(() => { 37 | elementsSet.add(el); 38 | callback(null); 39 | }), 40 | (err) => { 41 | test.error(err); 42 | test.strictSame(elementsSet, expectedElementsSet); 43 | test.end(); 44 | }, 45 | ); 46 | }); 47 | 48 | metatests.test('each with error', (test) => { 49 | const arr = [1, 2, 3, 4]; 50 | let count = 0; 51 | 52 | const elementsSet = new Set(); 53 | const expectedElementsCount = 2; 54 | const eachError = new Error('Each error'); 55 | 56 | metasync.each( 57 | arr, 58 | (el, callback) => 59 | process.nextTick(() => { 60 | elementsSet.add(el); 61 | count++; 62 | if (count === expectedElementsCount) { 63 | callback(eachError); 64 | } else { 65 | callback(null); 66 | } 67 | }), 68 | (err) => { 69 | test.strictSame(err, eachError); 70 | test.strictSame(elementsSet.size, expectedElementsCount); 71 | test.end(); 72 | }, 73 | ); 74 | }); 75 | -------------------------------------------------------------------------------- /test/array.every.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | const identity = (x, callback) => callback(null, x); 7 | 8 | const strictSameResult = (input, expectedResult, test, done) => { 9 | metasync.every(input, identity, (err, result) => { 10 | test.error(err); 11 | test.strictSame(result, expectedResult); 12 | 13 | done(); 14 | }); 15 | }; 16 | 17 | const fewStrictSameResult = (inOutPairs, test) => { 18 | let i = 0; 19 | const testsEnd = metasync.collect(inOutPairs.length); 20 | testsEnd.done(() => test.end()); 21 | const cb = () => testsEnd.pick('item' + i++); 22 | for (const [input, output] of inOutPairs) { 23 | strictSameResult(input, output, test, cb); 24 | } 25 | }; 26 | 27 | metatests.test('every with error', (test) => { 28 | const data = [1, 2, 3]; 29 | const everyErr = new Error('Every error'); 30 | 31 | const predicate = (item, callback) => { 32 | process.nextTick(() => 33 | item % 2 === 0 ? callback(everyErr) : callback(null, true), 34 | ); 35 | }; 36 | 37 | metasync.every(data, predicate, (err) => { 38 | test.strictSame(err, everyErr); 39 | test.end(); 40 | }); 41 | }); 42 | 43 | metatests.test('every with empty array', (test) => 44 | strictSameResult([], true, test, () => test.end()), 45 | ); 46 | 47 | metatests.test('every with one-element arrays', (test) => 48 | fewStrictSameResult( 49 | [ 50 | [[false], false], 51 | [[true], true], 52 | ], 53 | test, 54 | ), 55 | ); 56 | 57 | metatests.test('every with two-element arrays', (test) => 58 | fewStrictSameResult( 59 | [ 60 | [[false, false], false], 61 | [[false, true], false], 62 | [[true, false], false], 63 | [[true, true], true], 64 | ], 65 | test, 66 | ), 67 | ); 68 | 69 | metatests.test('every', (test) => { 70 | const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; 71 | 72 | const predicate = (item, callback) => { 73 | process.nextTick(() => callback(null, item > 0)); 74 | }; 75 | 76 | metasync.every(data, predicate, (err, result) => { 77 | test.error(err); 78 | test.strictSame(result, true); 79 | test.end(); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/array.filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('successful filter', (test) => { 7 | const arr = [ 8 | 'Lorem', 9 | 'ipsum', 10 | 'dolor', 11 | 'sit', 12 | 'amet', 13 | 'consectetur', 14 | 'adipiscing', 15 | 'elit', 16 | 'sed', 17 | 'do', 18 | 'eiusmod', 19 | 'tempor', 20 | 'incididunt', 21 | 'ut', 22 | 'labore', 23 | 'et', 24 | 'dolore', 25 | 'magna', 26 | 'aliqua', 27 | ]; 28 | const expectedArr = [ 29 | 'Lorem', 30 | 'ipsum', 31 | 'dolor', 32 | 'sit', 33 | 'amet', 34 | 'elit', 35 | 'sed', 36 | 'do', 37 | 'ut', 38 | 'et', 39 | 'magna', 40 | ]; 41 | 42 | metasync.filter( 43 | arr, 44 | (str, callback) => process.nextTick(() => callback(null, str.length < 6)), 45 | (err, res) => { 46 | test.error(err); 47 | test.same(res.join(), expectedArr.join()); 48 | test.end(); 49 | }, 50 | ); 51 | }); 52 | 53 | metatests.test('filter with empty array', (test) => { 54 | const arr = []; 55 | const expectedArr = []; 56 | 57 | metasync.filter( 58 | arr, 59 | (str, callback) => process.nextTick(() => callback(null, str.length < 6)), 60 | (err, res) => { 61 | test.error(err); 62 | test.strictSame(res, expectedArr); 63 | test.end(); 64 | }, 65 | ); 66 | }); 67 | 68 | metatests.test('successful filter', (test) => { 69 | const arr = [ 70 | 'Lorem', 71 | 'ipsum', 72 | 'dolor', 73 | 'sit', 74 | 'amet', 75 | 'consectetur', 76 | 'adipiscing', 77 | 'elit', 78 | 'sed', 79 | 'do', 80 | 'eiusmod', 81 | 'tempor', 82 | 'incididunt', 83 | 'ut', 84 | 'labore', 85 | 'et', 86 | 'dolore', 87 | 'magna', 88 | 'aliqua', 89 | ]; 90 | const filterError = new Error('Filter error'); 91 | const expectedArr = [ 92 | 'Lorem', 93 | 'ipsum', 94 | 'dolor', 95 | 'sit', 96 | 'amet', 97 | 'elit', 98 | 'sed', 99 | 'magna', 100 | ]; 101 | 102 | metasync.filter( 103 | arr, 104 | (str, callback) => 105 | process.nextTick(() => { 106 | if (str.length === 2) { 107 | callback(filterError); 108 | return; 109 | } 110 | callback(null, str.length < 6); 111 | }), 112 | (err, res) => { 113 | test.error(err); 114 | test.same(res.join(), expectedArr.join()); 115 | test.end(); 116 | }, 117 | ); 118 | }); 119 | -------------------------------------------------------------------------------- /test/array.find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('find with error', (test) => { 7 | const data = [1, 2, 3]; 8 | const expectedErrorMessage = 'Intentional error'; 9 | const predicate = (item, callback) => 10 | process.nextTick(() => { 11 | if (item % 2 === 0) { 12 | callback(new Error(expectedErrorMessage)); 13 | } else { 14 | callback(null, false); 15 | } 16 | }); 17 | 18 | metasync.find(data, predicate, (err) => { 19 | test.type(err, 'Error', 'err must be an instance of Error'); 20 | test.strictSame(err.message, expectedErrorMessage); 21 | test.end(); 22 | }); 23 | }); 24 | 25 | metatests.test('find', (test) => { 26 | const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; 27 | const expected = 15; 28 | const predicate = (item, callback) => 29 | process.nextTick(() => callback(null, item % 3 === 0 && item % 5 === 0)); 30 | 31 | metasync.find(data, predicate, (err, result) => { 32 | test.error(err, 'must not return an error'); 33 | test.strictSame(result, expected, `result should be: ${expected}`); 34 | test.end(); 35 | }); 36 | }); 37 | 38 | metatests.test('with empty array', (test) => { 39 | metasync.find( 40 | [], 41 | (el, callback) => process.nextTick(() => callback(null, true)), 42 | (err, result) => { 43 | test.error(err); 44 | test.strictSame(result, undefined); 45 | test.end(); 46 | }, 47 | ); 48 | }); 49 | 50 | metatests.test('with array without element which is searching', (test) => { 51 | const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; 52 | metasync.find( 53 | data, 54 | (el, callback) => process.nextTick(() => callback(null, el === 20)), 55 | (err, result) => { 56 | test.error(err); 57 | test.strictSame(result, undefined); 58 | test.end(); 59 | }, 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /test/array.map.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('succesfull map', (test) => { 7 | const arr = [1, 2, 3]; 8 | const expectedArr = [1, 4, 9]; 9 | 10 | metasync.map( 11 | arr, 12 | (x, callback) => process.nextTick(() => callback(null, x * x)), 13 | (err, res) => { 14 | test.error(err); 15 | test.strictSame(res, expectedArr); 16 | test.end(); 17 | }, 18 | ); 19 | }); 20 | 21 | metatests.test('map with empty array', (test) => { 22 | const arr = []; 23 | const expectedArr = []; 24 | 25 | metasync.map( 26 | arr, 27 | (x, callback) => process.nextTick(() => callback(null, x * x)), 28 | (err, res) => { 29 | test.error(err); 30 | test.strictSame(res, expectedArr); 31 | test.end(); 32 | }, 33 | ); 34 | }); 35 | 36 | metatests.test('map with error', (test) => { 37 | const arr = [1, 2, 3]; 38 | const mapError = new Error('Map error'); 39 | let count = 0; 40 | 41 | metasync.map( 42 | arr, 43 | (x, callback) => 44 | process.nextTick(() => { 45 | count++; 46 | if (count === 2) { 47 | callback(mapError); 48 | return; 49 | } 50 | callback(null, x * x); 51 | }), 52 | (err, res) => { 53 | test.strictSame(err, mapError); 54 | test.strictSame(res, undefined); 55 | test.end(); 56 | }, 57 | ); 58 | }); 59 | -------------------------------------------------------------------------------- /test/array.reduce.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('reduce with initial', (test) => { 7 | const arr = [1, 2, 3, 4, 5]; 8 | const initial = 10; 9 | const expectedRes = 25; 10 | 11 | metasync.reduce( 12 | arr, 13 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 14 | (err, res) => { 15 | test.error(err); 16 | test.strictSame(res, expectedRes); 17 | test.end(); 18 | }, 19 | initial, 20 | ); 21 | }); 22 | 23 | metatests.test('reduce with initial and empty array', (test) => { 24 | const arr = []; 25 | const initial = 10; 26 | const expectedRes = 10; 27 | 28 | metasync.reduce( 29 | arr, 30 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 31 | (err, res) => { 32 | test.error(err); 33 | test.strictSame(res, expectedRes); 34 | test.end(); 35 | }, 36 | initial, 37 | ); 38 | }); 39 | 40 | metatests.test('reduce without initial and with empty array', (test) => { 41 | const arr = []; 42 | const expectedError = new TypeError( 43 | 'Metasync: reduce of empty array with no initial value', 44 | ); 45 | 46 | metasync.reduce( 47 | arr, 48 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 49 | (err, res) => { 50 | test.strictSame(err, expectedError); 51 | test.strictSame(res, undefined); 52 | test.end(); 53 | }, 54 | ); 55 | }); 56 | 57 | metatests.test('reduce single-element array without initial', (test) => { 58 | const arr = [2]; 59 | 60 | metasync.reduce( 61 | arr, 62 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 63 | (err, res) => { 64 | test.strictSame(err, null); 65 | test.strictSame(res, 2); 66 | test.end(); 67 | }, 68 | ); 69 | }); 70 | 71 | metatests.test('reduce without initial', (test) => { 72 | const arr = [1, 2, 3, 4, 5]; 73 | const expectedRes = 15; 74 | 75 | metasync.reduce( 76 | arr, 77 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 78 | (err, res) => { 79 | test.error(err); 80 | test.strictSame(res, expectedRes); 81 | test.end(); 82 | }, 83 | ); 84 | }); 85 | 86 | metatests.test('reduce with asymetric function', (test) => { 87 | const arr = '10110011'; 88 | const expectedRes = 179; 89 | 90 | metasync.reduce( 91 | arr, 92 | (prev, cur, callback) => 93 | process.nextTick(() => callback(null, prev * 2 + +cur)), 94 | (err, res) => { 95 | test.error(err); 96 | test.strictSame(res, expectedRes); 97 | test.end(); 98 | }, 99 | ); 100 | }); 101 | 102 | metatests.test('reduce with error', (test) => { 103 | const arr = '10120011'; 104 | const reduceError = new Error('Reduce error'); 105 | 106 | metasync.reduce( 107 | arr, 108 | (prev, cur, callback) => 109 | process.nextTick(() => { 110 | const digit = +cur; 111 | if (digit > 1) { 112 | callback(reduceError); 113 | return; 114 | } 115 | callback(null, prev * 2 + digit); 116 | }), 117 | (err, res) => { 118 | test.strictSame(err, reduceError); 119 | test.strictSame(res, undefined); 120 | test.end(); 121 | }, 122 | ); 123 | }); 124 | -------------------------------------------------------------------------------- /test/array.reduceRight.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('reduceRight with initial', (test) => { 7 | const arr = [1, 2, 3, 4, 5]; 8 | const initial = 10; 9 | const expectedRes = 25; 10 | 11 | metasync.reduceRight( 12 | arr, 13 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 14 | (err, res) => { 15 | test.error(err); 16 | test.strictSame(res, expectedRes); 17 | test.end(); 18 | }, 19 | initial, 20 | ); 21 | }); 22 | 23 | metatests.test('reduceRight with initial and empty array', (test) => { 24 | const arr = []; 25 | const initial = 10; 26 | const expectedRes = 10; 27 | 28 | metasync.reduceRight( 29 | arr, 30 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 31 | (err, res) => { 32 | test.error(err); 33 | test.strictSame(res, expectedRes); 34 | test.end(); 35 | }, 36 | initial, 37 | ); 38 | }); 39 | 40 | metatests.test('reduceRight without initial and with empty array', (test) => { 41 | const arr = []; 42 | const expectedError = new TypeError( 43 | 'Metasync: reduceRight of empty array with no initial value', 44 | ); 45 | 46 | metasync.reduceRight( 47 | arr, 48 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 49 | (err, res) => { 50 | test.strictSame(err, expectedError); 51 | test.strictSame(res, undefined); 52 | test.end(); 53 | }, 54 | ); 55 | }); 56 | 57 | metatests.test( 58 | 'reduceRight without initial and with single-element array', 59 | (test) => { 60 | const arr = [2]; 61 | 62 | metasync.reduceRight( 63 | arr, 64 | (prev, cur, callback) => 65 | process.nextTick(() => callback(null, prev + cur)), 66 | (err, res) => { 67 | test.strictSame(err, null); 68 | test.strictSame(res, 2); 69 | test.end(); 70 | }, 71 | ); 72 | }, 73 | ); 74 | 75 | metatests.test('reduceRight without initial', (test) => { 76 | const arr = [1, 2, 3, 4, 5]; 77 | const expectedRes = 15; 78 | 79 | metasync.reduceRight( 80 | arr, 81 | (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), 82 | (err, res) => { 83 | test.error(err); 84 | test.strictSame(res, expectedRes); 85 | test.end(); 86 | }, 87 | ); 88 | }); 89 | 90 | metatests.test('reduceRight with asymetric function', (test) => { 91 | const arr = '10110011'; 92 | const expectedRes = 205; 93 | 94 | metasync.reduceRight( 95 | arr, 96 | (prev, cur, callback) => 97 | process.nextTick(() => callback(null, prev * 2 + +cur)), 98 | (err, res) => { 99 | test.error(err); 100 | test.strictSame(res, expectedRes); 101 | test.end(); 102 | }, 103 | ); 104 | }); 105 | 106 | metatests.test('reduceRight with error', (test) => { 107 | const arr = '10120011'; 108 | const reduceError = new Error('Reduce error'); 109 | 110 | metasync.reduce( 111 | arr, 112 | (prev, cur, callback) => 113 | process.nextTick(() => { 114 | const digit = +cur; 115 | if (digit > 1) { 116 | callback(reduceError); 117 | return; 118 | } 119 | callback(null, prev * 2 + digit); 120 | }), 121 | (err, res) => { 122 | test.strictSame(err, reduceError); 123 | test.strictSame(res, undefined); 124 | test.end(); 125 | }, 126 | ); 127 | }); 128 | -------------------------------------------------------------------------------- /test/array.series.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('successful series', (test) => { 7 | const arr = [1, 2, 3, 4]; 8 | const expectedElements = arr; 9 | const elements = []; 10 | metasync.series( 11 | arr, 12 | (el, callback) => { 13 | elements.push(el); 14 | callback(null); 15 | }, 16 | (err) => { 17 | test.error(err); 18 | test.strictSame(elements, expectedElements); 19 | test.end(); 20 | }, 21 | ); 22 | }); 23 | 24 | metatests.test('series with error', (test) => { 25 | const arr = [1, 2, 3, 4]; 26 | const expectedElements = [1, 2]; 27 | const expectedElementsCount = 2; 28 | 29 | const elements = []; 30 | let count = 0; 31 | const seriesError = new Error('seriesError'); 32 | 33 | metasync.series( 34 | arr, 35 | (el, callback) => { 36 | elements.push(el); 37 | count++; 38 | if (count === expectedElementsCount) { 39 | callback(seriesError); 40 | } else { 41 | callback(null); 42 | } 43 | }, 44 | (err) => { 45 | test.strictSame(err, seriesError); 46 | test.strictSame(elements, expectedElements); 47 | test.end(); 48 | }, 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /test/array.some.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('successful some', (test) => { 7 | const arr = [1, 2, 3]; 8 | 9 | const predicate = (x, callback) => callback(null, x % 2 === 0); 10 | metasync.some(arr, predicate, (err, accepted) => { 11 | test.error(err); 12 | test.strictSame(accepted, true); 13 | test.end(); 14 | }); 15 | }); 16 | 17 | metatests.test('failing some', (test) => { 18 | const arr = [1, 2, 3]; 19 | 20 | const predicate = (x, callback) => callback(null, x > 3); 21 | metasync.some(arr, predicate, (err, accepted) => { 22 | test.error(err); 23 | test.strictSame(accepted, false); 24 | test.end(); 25 | }); 26 | }); 27 | 28 | metatests.test('erroneous some', (test) => { 29 | const arr = [1, 2, 3]; 30 | const someError = new Error('Some error'); 31 | 32 | const predicate = (x, callback) => 33 | x % 2 === 0 ? callback(someError) : callback(null, false); 34 | metasync.some(arr, predicate, (err, accepted) => { 35 | test.strictSame(err, someError); 36 | test.strictSame(accepted, undefined); 37 | test.end(); 38 | }); 39 | }); 40 | 41 | metatests.test('some with empty array', (test) => { 42 | const arr = []; 43 | 44 | const predicate = (x, callback) => callback(null, x > 3); 45 | metasync.some(arr, predicate, (err, accepted) => { 46 | test.error(err); 47 | test.strictSame(accepted, false); 48 | test.end(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/collectors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('data collector', (test) => { 7 | const expectedResult = { 8 | key1: 1, 9 | key2: 2, 10 | key3: 3, 11 | }; 12 | 13 | const dc = metasync 14 | .collect(3) 15 | .done((err, result) => { 16 | test.error(err); 17 | test.strictSame(result, expectedResult); 18 | test.end(); 19 | }) 20 | .timeout(1000); 21 | 22 | dc.collect('key1', null, 1); 23 | dc.collect('key2', null, 2); 24 | dc.collect('key3', null, 3); 25 | }); 26 | 27 | metatests.test('data collector', (test) => { 28 | const expectedResult = { 29 | key1: 1, 30 | key2: 2, 31 | key3: 3, 32 | }; 33 | 34 | const kc = metasync 35 | .collect(['key1', 'key2', 'key3']) 36 | .done((err, result) => { 37 | test.error(err); 38 | test.strictSame(result, expectedResult); 39 | test.end(); 40 | }) 41 | .timeout(); 42 | 43 | kc.collect('key1', null, 1); 44 | kc.collect('key2', null, 2); 45 | kc.collect('key3', null, 3); 46 | }); 47 | 48 | metatests.test('distinct data collector', (test) => { 49 | const expectedResult = { 50 | key1: 2, 51 | key2: 2, 52 | key3: 3, 53 | }; 54 | 55 | const dc = metasync 56 | .collect(3) 57 | .distinct() 58 | .done((err, result) => { 59 | test.error(err); 60 | test.strictSame(result, expectedResult); 61 | test.end(); 62 | }); 63 | 64 | dc.pick('key1', 1); 65 | dc.pick('key1', 2); 66 | dc.pick('key2', 2); 67 | dc.pick('key3', 3); 68 | }); 69 | 70 | metatests.test('distinct key collector', (test) => { 71 | const expectedResult = { 72 | key1: 2, 73 | key2: 2, 74 | key3: 3, 75 | }; 76 | 77 | const kc = metasync 78 | .collect(['key1', 'key2', 'key3']) 79 | .distinct() 80 | .done((err, result) => { 81 | test.error(err); 82 | test.strictSame(result, expectedResult); 83 | test.end(); 84 | }); 85 | 86 | kc.pick('key1', 1); 87 | kc.pick('key1', 2); 88 | kc.pick('key2', 2); 89 | kc.pick('key3', 3); 90 | }); 91 | 92 | metatests.test('data collector with repeated keys', (test) => { 93 | const dc = metasync 94 | .collect(3) 95 | .timeout(100) 96 | .done((err) => { 97 | test.assert(err); 98 | test.end(); 99 | }); 100 | 101 | dc.collect('key1', null, 1); 102 | dc.collect('key1', null, 2); 103 | dc.collect('key2', null, 2); 104 | }); 105 | 106 | metatests.test('key collector with repeated keys', (test) => { 107 | const kc = metasync 108 | .collect(['key1', 'key2', 'key3']) 109 | .timeout(100) 110 | .done((err) => { 111 | test.assert(err); 112 | test.end(); 113 | }); 114 | 115 | kc.collect('key1', null, 1); 116 | kc.collect('key1', null, 2); 117 | kc.collect('key2', null, 2); 118 | }); 119 | 120 | metatests.test('collect with error', (test) => { 121 | const testErr = new Error('Test error'); 122 | const col = metasync.collect(1); 123 | col.done((err, res) => { 124 | test.strictSame(err, testErr); 125 | test.strictSame(res, {}); 126 | test.end(); 127 | }); 128 | col.fail('someKey', testErr); 129 | }); 130 | 131 | metatests.test(`collect method calling after it's done`, (test) => { 132 | const col = metasync.collect(1); 133 | col.done((err, res) => { 134 | test.error(err); 135 | test.strictSame(res, { someKey: 'someVal' }); 136 | test.end(); 137 | }); 138 | col.pick('someKey', 'someVal'); 139 | col.pick('someKey2', 'someVal2'); 140 | }); 141 | 142 | metatests.test('keys collector receives wrong key', (test) => { 143 | const col = metasync.collect(['rightKey']); 144 | col.done((err, res) => { 145 | test.error(err); 146 | test.strictSame(res, { wrongKey: 'someVal', rightKey: 'someVal' }); 147 | test.end(); 148 | }); 149 | col.pick('wrongKey', 'someVal'); 150 | col.pick('rightKey', 'someVal'); 151 | }); 152 | 153 | metatests.test('distinct keys collector receives wrong key', (test) => { 154 | const col = metasync.collect(['rightKey']).distinct(); 155 | col.done((err) => { 156 | test.assert(err); 157 | test.end(); 158 | }); 159 | col.pick('wrongKey', 'someVal'); 160 | col.pick('rightKey', 'someVal'); 161 | }); 162 | 163 | metatests.test('collect with take', (test) => { 164 | const col = metasync.collect(1); 165 | col.done((err, res) => { 166 | test.error(err); 167 | test.strictSame(res, { someKey: 'someVal' }); 168 | test.end(); 169 | }); 170 | const af = (x, callback) => callback(null, x); 171 | col.take('someKey', af, 'someVal'); 172 | }); 173 | 174 | metatests.test('collect with timeout error', (test) => { 175 | const timeoutErr = new Error('Metasync: Collector timed out'); 176 | const col = metasync 177 | .collect(1) 178 | .done((err, res) => { 179 | test.strictSame(err, timeoutErr); 180 | test.strictSame(res, {}); 181 | test.end(); 182 | }) 183 | .timeout(1); 184 | const af = (x, callback) => setTimeout(() => callback(null, x), 2); 185 | col.take('someKey', af, 'someVal'); 186 | }); 187 | 188 | metatests.test('collect with take calls bigger than expected', (test) => { 189 | const col = metasync.collect(1).done((err, res) => { 190 | test.error(err); 191 | test.strictSame(Object.keys(res).length, 1); 192 | test.end(); 193 | }); 194 | const af = (x, callback) => setTimeout(() => callback(null, x), 1); 195 | col.take('someKey', af, 'someVal'); 196 | col.take('someKey2', af, 'someVal2'); 197 | }); 198 | 199 | metatests.test('cancel data collector', (test) => { 200 | const dc = metasync.collect(3).done((err) => { 201 | test.assert(err); 202 | test.end(); 203 | }); 204 | 205 | dc.pick('key', 'value'); 206 | dc.cancel(); 207 | }); 208 | 209 | metatests.test('cancel key collector', (test) => { 210 | const dc = metasync.collect(['uno', 'due']).done((err) => { 211 | test.assert(err); 212 | test.end(); 213 | }); 214 | 215 | dc.pick('key', 'value'); 216 | dc.cancel(); 217 | }); 218 | 219 | metatests.test('collect then success', (test) => { 220 | const col = metasync.collect(1).then( 221 | (result) => { 222 | test.assert(result); 223 | test.end(); 224 | }, 225 | (err) => { 226 | test.error(err); 227 | test.end(); 228 | }, 229 | ); 230 | col.pick('Key', 'value'); 231 | }); 232 | 233 | metatests.test('collect then fail', (test) => { 234 | metasync 235 | .collect(5) 236 | .timeout(10) 237 | .then( 238 | (result) => { 239 | test.error(result); 240 | test.end(); 241 | }, 242 | (err) => { 243 | test.assert(err); 244 | test.end(); 245 | }, 246 | ); 247 | }); 248 | -------------------------------------------------------------------------------- /test/compose.clone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('async functions composition clone', (test) => { 7 | const data = { test: 'data' }; 8 | const expectedData = { test: 'data', data1: 'data 1', data2: 'data 2' }; 9 | 10 | const fn1 = (data, cb) => { 11 | process.nextTick(() => { 12 | cb(null, { data1: 'data 1' }); 13 | }); 14 | }; 15 | 16 | const fn2 = (data, cb) => { 17 | process.nextTick(() => { 18 | cb(null, { data2: 'data 2' }); 19 | }); 20 | }; 21 | 22 | const fc1 = metasync([[fn1, fn2]]); 23 | const fc2 = fc1.clone(); 24 | 25 | fc1(data, (err, data) => { 26 | test.error(err); 27 | test.strictSame(data, expectedData); 28 | fc2(data, (err, data) => { 29 | test.error(err); 30 | test.strictSame(data, expectedData); 31 | test.end(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/compose.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('async parallel functions composition', (test) => { 7 | const data = { test: 'data' }; 8 | const expectedData = { test: 'data', data1: 'data 1', data2: 'data 2' }; 9 | 10 | const fn1 = (data, cb) => { 11 | process.nextTick(() => { 12 | cb(null, { data1: 'data 1' }); 13 | }); 14 | }; 15 | 16 | const fn2 = (data, cb) => { 17 | process.nextTick(() => { 18 | cb(null, { data2: 'data 2' }); 19 | }); 20 | }; 21 | 22 | const fc = metasync([[fn1, fn2]]); 23 | 24 | fc(data, (err, data) => { 25 | test.error(err); 26 | test.strictSame(data, expectedData); 27 | test.end(); 28 | }); 29 | }); 30 | 31 | metatests.test('async complex functions composition', (test) => { 32 | const data = { test: 'data' }; 33 | const expectedDataInFn1 = { test: 'data' }; 34 | const expectedDataInFn2 = { test: 'data', data1: 'data 1' }; 35 | const expectedDataInRes = { test: 'data' }; 36 | 37 | let i; 38 | for (i = 1; i < 6; i++) { 39 | expectedDataInRes['data' + i] = 'data ' + i; 40 | } 41 | 42 | const fn1 = (data, cb) => { 43 | process.nextTick(() => { 44 | test.strictSame(data, expectedDataInFn1); 45 | cb(null, { data1: 'data 1' }); 46 | }); 47 | }; 48 | 49 | const fn2 = (data, cb) => { 50 | process.nextTick(() => { 51 | test.strictSame(data, expectedDataInFn2); 52 | cb(null, { data2: 'data 2' }); 53 | }); 54 | }; 55 | 56 | const fn3 = (data, cb) => { 57 | process.nextTick(() => { 58 | cb(null, { data3: 'data 3' }); 59 | }); 60 | }; 61 | 62 | const fn4 = (data, cb) => { 63 | process.nextTick(() => { 64 | cb(null, { data4: 'data 4' }); 65 | }); 66 | }; 67 | 68 | const fn5 = (data, cb) => { 69 | process.nextTick(() => { 70 | test.strictSame(data.data1, 'data 1'); 71 | test.strictSame(data.data2, 'data 2'); 72 | test.strictSame(data.data3, 'data 3'); 73 | test.strictSame(data.data4, 'data 4'); 74 | cb(null, { data5: 'data 5' }); 75 | }); 76 | }; 77 | 78 | const fc = metasync([fn1, fn2, [[fn3, [fn4, fn5]]], [], [[]]]); 79 | fc(data, (err, data) => { 80 | test.error(err); 81 | test.strictSame(data, expectedDataInRes); 82 | test.end(); 83 | }); 84 | }); 85 | 86 | const AC1 = 'async functions composition cancel before start'; 87 | metatests.test(AC1, (test) => { 88 | let count = 0; 89 | 90 | const fn1 = (data, cb) => { 91 | count++; 92 | process.nextTick(() => cb(null, 'data 1')); 93 | }; 94 | 95 | const fn2 = (data, cb) => { 96 | count++; 97 | process.nextTick(() => cb(null, 'data 2')); 98 | }; 99 | 100 | const fn3 = (data, cb) => { 101 | count++; 102 | process.nextTick(() => cb(null, 'data 3')); 103 | }; 104 | 105 | const fn4 = (data, cb) => { 106 | count++; 107 | process.nextTick(() => cb(null, 'data 4')); 108 | }; 109 | 110 | const fc = metasync([fn1, [[fn2, fn3]], fn4]); 111 | fc.cancel(); 112 | fc({}, (err, data) => { 113 | test.isError(err); 114 | test.strictSame(data, undefined); 115 | test.strictSame(count, 0); 116 | test.end(); 117 | }); 118 | }); 119 | 120 | const AC2 = 'async functions composition cancel in the middle'; 121 | metatests.test(AC2, (test) => { 122 | let count = 0; 123 | let finished = 0; 124 | 125 | const fn1 = (data, cb) => { 126 | count++; 127 | process.nextTick(() => { 128 | finished++; 129 | cb(null, 'data 1'); 130 | }); 131 | }; 132 | 133 | const fn2 = (data, cb) => { 134 | count++; 135 | setTimeout(() => { 136 | finished++; 137 | cb(null, 'data 2'); 138 | }, 200); 139 | }; 140 | 141 | const fn3 = (data, cb) => { 142 | count++; 143 | setTimeout(() => { 144 | finished++; 145 | cb(null, 'data 3'); 146 | }, 200); 147 | }; 148 | 149 | const fn4 = (data, cb) => { 150 | count++; 151 | setTimeout(() => { 152 | finished++; 153 | cb(null, 'data 4'); 154 | }, 200); 155 | }; 156 | 157 | const fc = metasync([fn1, [[fn2, fn3]], fn4]); 158 | fc({}, (err, data) => { 159 | test.isError(err); 160 | test.strictSame(data, undefined); 161 | test.strictSame(count, 3); 162 | test.strictSame(finished, 1); 163 | test.end(); 164 | }); 165 | 166 | setTimeout(() => { 167 | fc.cancel(); 168 | }, 100); 169 | }); 170 | 171 | metatests.test('async functions composition cancel after end', (test) => { 172 | let count = 0; 173 | 174 | const fn1 = (data, cb) => { 175 | count++; 176 | process.nextTick(() => { 177 | cb(null, { data1: 'data 1' }); 178 | }); 179 | }; 180 | 181 | const fn2 = (data, cb) => { 182 | count++; 183 | process.nextTick(() => { 184 | cb(null, { data2: 'data 2' }); 185 | }); 186 | }; 187 | 188 | const fn3 = (data, cb) => { 189 | count++; 190 | process.nextTick(() => { 191 | cb(null, { data3: 'data 3' }); 192 | }); 193 | }; 194 | 195 | const fn4 = (data, cb) => { 196 | count++; 197 | process.nextTick(() => { 198 | cb(null, { data4: 'data 4' }); 199 | }); 200 | }; 201 | 202 | const fc = metasync([fn1, [[fn2, fn3]], fn4]); 203 | fc({}, (err, data) => { 204 | test.error(err); 205 | test.strictSame(data, { 206 | data1: 'data 1', 207 | data2: 'data 2', 208 | data3: 'data 3', 209 | data4: 'data 4', 210 | }); 211 | test.strictSame(count, 4); 212 | test.end(); 213 | }); 214 | 215 | setTimeout(() => { 216 | fc.cancel(); 217 | }, 100); 218 | }); 219 | 220 | metatests.test('async functions composition to array', (test) => { 221 | let count = 0; 222 | 223 | const fn1 = (data, cb) => { 224 | count++; 225 | process.nextTick(() => cb(null, 'data 1')); 226 | }; 227 | 228 | const fn2 = (data, cb) => { 229 | count++; 230 | process.nextTick(() => cb(null, 'data 2')); 231 | }; 232 | 233 | const fn3 = (data, cb) => { 234 | count++; 235 | process.nextTick(() => cb(null, 'data 3')); 236 | }; 237 | 238 | const fn4 = (data, cb) => { 239 | count++; 240 | process.nextTick(() => cb(null, 'data 4')); 241 | }; 242 | 243 | const fc = metasync([fn1, [[fn2, fn3]], fn4]); 244 | fc(['data 0'], (err, data) => { 245 | test.error(err); 246 | test.strictSame(data, ['data 0', 'data 1', 'data 2', 'data 3', 'data 4']); 247 | test.strictSame(count, 4); 248 | test.end(); 249 | }); 250 | }); 251 | -------------------------------------------------------------------------------- /test/compose.then.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('successful then', (test) => { 7 | let finishedFuncsCount = 0; 8 | const res1 = 'res1'; 9 | const af1 = (data, callback) => 10 | process.nextTick(() => { 11 | test.strictSame(data, {}); 12 | test.strictSame(finishedFuncsCount, 0); 13 | finishedFuncsCount++; 14 | callback(null, { res1 }); 15 | }); 16 | const res2 = 'res2'; 17 | const af2 = (data, callback) => 18 | process.nextTick(() => { 19 | test.strictSame(data, { res1 }); 20 | test.strictSame(finishedFuncsCount, 1); 21 | finishedFuncsCount++; 22 | callback(null, { res2 }); 23 | }); 24 | const res3 = 'res3'; 25 | const af3 = (data, callback) => 26 | process.nextTick(() => { 27 | const keysCount = Object.keys(data).length; 28 | test.ok(keysCount >= 2 && keysCount < 4); 29 | test.ok(finishedFuncsCount >= 2 && finishedFuncsCount < 4); 30 | finishedFuncsCount++; 31 | callback(null, { res3 }); 32 | }); 33 | const res4 = 'res4'; 34 | const af4 = (data, callback) => 35 | process.nextTick(() => { 36 | const keysCount = Object.keys(data).length; 37 | test.ok(keysCount >= 2 && keysCount < 4); 38 | test.ok(finishedFuncsCount >= 2 && finishedFuncsCount < 4); 39 | finishedFuncsCount++; 40 | callback(null, { res4 }); 41 | }); 42 | const faf1 = metasync([af1, [[af2, af3]], af4]); 43 | faf1().then( 44 | (res) => { 45 | test.strictSame(res, { 46 | res1: 'res1', 47 | res2: 'res2', 48 | res3: 'res3', 49 | res4: 'res4', 50 | }); 51 | test.strictSame(finishedFuncsCount, 4); 52 | test.end(); 53 | }, 54 | (err) => { 55 | test.error(err); 56 | }, 57 | ); 58 | }); 59 | -------------------------------------------------------------------------------- /test/composition.cancel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | // Emulate Asynchronous calls of function 7 | // callback - function 8 | const wrapAsync = (callback) => { 9 | setTimeout(callback, Math.floor(Math.random() * 500)); 10 | }; 11 | 12 | metatests.test('async functions composition cancel in the middle', (test) => { 13 | let fc = null; 14 | const fn1 = test.mustCall((data, cb) => { 15 | wrapAsync(() => { 16 | cb(null, 1); 17 | }); 18 | }); 19 | 20 | const fn2 = test.mustCall((data, cb) => { 21 | test.strictSame(data, [1]); 22 | wrapAsync(() => { 23 | if (fc) fc.cancel(); 24 | cb(null, 2); 25 | }); 26 | }); 27 | 28 | const fn3 = test.mustNotCall(); 29 | const fn4 = test.mustNotCall(); 30 | 31 | fc = metasync([fn1, fn2, fn3, fn4]); 32 | 33 | fc([], (err, data) => { 34 | test.isError(err, new Error('Metasync: asynchronous composition canceled')); 35 | test.strictSame(data, undefined); 36 | test.end(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/composition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | const fs = require('fs'); 6 | 7 | const getPerson = (context, cb) => { 8 | const persons = [ 9 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 10 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 11 | ]; 12 | const person = persons.find((p) => p.name === context.name); 13 | cb(null, { person }); 14 | }; 15 | 16 | const lookupCountry = (context, cb) => { 17 | const dictionary = { 18 | Rome: 'Roman Empire', 19 | Shaoshan: 'Quin Empire', 20 | }; 21 | const country = dictionary[context.person.city]; 22 | cb(null, { country }); 23 | }; 24 | 25 | const prepareResult = (context, cb) => { 26 | const result = Object.assign({}, context.person, { 27 | country: context.country, 28 | length: context.buffer.length, 29 | }); 30 | cb(null, { result }); 31 | }; 32 | 33 | metatests.test('sync complex functions composition', (test) => { 34 | const readFile = (context, cb) => { 35 | fs.readFile(context.file, (err, buffer) => { 36 | test.error(err); 37 | cb(null, { buffer }); 38 | }); 39 | }; 40 | 41 | const fc = metasync([getPerson, [[lookupCountry, readFile]], prepareResult]); 42 | 43 | fc( 44 | { 45 | name: 'Mao Zedong', 46 | file: './AUTHORS', 47 | }, 48 | (err, context) => { 49 | test.error(err); 50 | const expected = { 51 | name: 'Mao Zedong', 52 | city: 'Shaoshan', 53 | born: 1893, 54 | country: 'Quin Empire', 55 | length: 318, 56 | }; 57 | test.strictSame(context.result, expected); 58 | test.end(); 59 | }, 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /test/composition.parallel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const metasync = require('..'); 5 | 6 | // Emulate Asynchronous calls of function 7 | // callback - function 8 | const wrapAsync = (callback) => { 9 | setTimeout(callback, Math.floor(Math.random() * 500)); 10 | }; 11 | 12 | metatests.test('async complex functions composition', (test) => { 13 | const fn1 = test.mustCall((data, cb) => { 14 | wrapAsync(() => { 15 | cb(null, 1); 16 | }); 17 | }); 18 | 19 | const fn2 = test.mustCall((data, cb) => { 20 | wrapAsync(() => { 21 | cb(null, 2); 22 | }); 23 | }); 24 | 25 | const fn3 = test.mustCall((data, cb) => { 26 | test.strictSame(data.length, 2); 27 | wrapAsync(() => { 28 | cb(null, 3); 29 | }); 30 | }); 31 | 32 | const fn4 = test.mustCall((data, cb) => { 33 | wrapAsync(() => { 34 | cb(null, 4); 35 | }); 36 | }); 37 | 38 | const fc = metasync([[[fn1, fn2]], fn3, fn4]); 39 | 40 | fc([], (err, data) => { 41 | test.error(err); 42 | test.strictSame(data.length, 4); 43 | test.end(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/composition.pause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const metasync = require('..'); 5 | const fs = require('fs'); 6 | 7 | const getPerson = (context, cb) => { 8 | const persons = [ 9 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 10 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 11 | ]; 12 | const person = persons.find((p) => p.name === context.name); 13 | cb(null, { person }); 14 | }; 15 | 16 | const lookupCountry = (context, cb) => { 17 | const dictionary = { 18 | Rome: 'Roman Empire', 19 | Shaoshan: 'Quin Empire', 20 | }; 21 | const country = dictionary[context.person.city]; 22 | cb(null, { country }); 23 | }; 24 | 25 | const prepareResult = (context, cb) => { 26 | const result = Object.assign({}, context.person, { 27 | country: context.country, 28 | length: context.buffer.length, 29 | }); 30 | cb(null, { result }); 31 | }; 32 | 33 | metatests.test('async functions composition pause in the middle', (test) => { 34 | const readFile = (context, cb) => { 35 | fs.readFile(context.file, (err, buffer) => { 36 | test.error(err); 37 | cb(null, { buffer }); 38 | }); 39 | }; 40 | 41 | const fc = metasync([getPerson, [[lookupCountry, readFile]], prepareResult]); 42 | 43 | fc( 44 | { 45 | name: 'Mao Zedong', 46 | file: './AUTHORS', 47 | }, 48 | (err, context) => { 49 | test.error(err); 50 | const expected = { 51 | name: 'Mao Zedong', 52 | city: 'Shaoshan', 53 | born: 1893, 54 | country: 'Quin Empire', 55 | length: 318, 56 | }; 57 | test.strictSame(context.result, expected); 58 | test.end(); 59 | }, 60 | ); 61 | 62 | process.nextTick(() => { 63 | fc.pause(); 64 | setTimeout(() => { 65 | fc.resume(); 66 | }, 1000); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/composition.sequential.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metatests = require('metatests'); 4 | const metasync = require('..'); 5 | 6 | metatests.test('sequential functions', (test) => { 7 | const f1 = test.mustCall((c, cb) => cb()); 8 | const f2 = test.mustCall((c, cb) => cb()); 9 | const f3 = test.mustCall((c, cb) => cb()); 10 | const f4 = test.mustCall((c, cb) => cb()); 11 | 12 | const fc = metasync([f1, f2, f3, f4]); 13 | 14 | fc((err, context) => { 15 | test.error(err); 16 | test.strictSame(context, {}); 17 | test.end(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/control.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('firstOf', (test) => { 7 | const returningFnIndex = 2; 8 | let dataReturned = false; 9 | 10 | const execUnlessDataReturned = (data) => (callback) => { 11 | if (dataReturned) { 12 | callback(null, data); 13 | } else { 14 | process.nextTick(execUnlessDataReturned); 15 | } 16 | }; 17 | const makeIFn = (i) => (callback) => 18 | process.nextTick(() => { 19 | const iData = 'data' + i; 20 | if (i === returningFnIndex) { 21 | dataReturned = true; 22 | callback(null, iData); 23 | } else { 24 | execUnlessDataReturned(iData); 25 | } 26 | }); 27 | 28 | const fns = [1, 2, 3].map(makeIFn); 29 | 30 | metasync.firstOf(fns, (err, data) => { 31 | test.error(err); 32 | test.strictSame(data, 'data2'); 33 | test.end(); 34 | }); 35 | }); 36 | 37 | metatests.test('parallel with error', (test) => { 38 | const parallelError = new Error('Parallel error'); 39 | 40 | const fn1 = (data, cb) => { 41 | process.nextTick(() => { 42 | cb(null, { data1: 'data 1' }); 43 | }); 44 | }; 45 | 46 | const fn2 = (data, cb) => { 47 | process.nextTick(() => { 48 | cb(parallelError); 49 | }); 50 | }; 51 | 52 | metasync.parallel([fn1, fn2], (err, res) => { 53 | test.strictSame(err, parallelError); 54 | test.strictSame(res, undefined); 55 | test.end(); 56 | }); 57 | }); 58 | 59 | metatests.test('sequential with error', (test) => { 60 | const sequentialError = new Error('Sequential error'); 61 | const expectedDataInFn2 = { data1: 'data 1' }; 62 | 63 | const fn1 = (data, cb) => { 64 | process.nextTick(() => { 65 | cb(null, { data1: 'data 1' }); 66 | }); 67 | }; 68 | 69 | const fn2 = (data, cb) => { 70 | process.nextTick(() => { 71 | test.same(data, expectedDataInFn2); 72 | cb(sequentialError); 73 | }); 74 | }; 75 | 76 | metasync.sequential([fn1, fn2], (err, res) => { 77 | test.strictSame(err, sequentialError); 78 | test.strictSame(res, undefined); 79 | test.end(); 80 | }); 81 | }); 82 | 83 | metatests.test('runIf run asyncFn', (test) => { 84 | const asyncFn = test.mustCall((arg1, arg2, cb) => { 85 | process.nextTick(() => { 86 | cb(null, { arg1, arg2 }); 87 | }); 88 | }); 89 | 90 | metasync.runIf(true, asyncFn, 'val1', 'val2', (err, result) => { 91 | test.error(err); 92 | test.strictSame(result, { arg1: 'val1', arg2: 'val2' }); 93 | test.end(); 94 | }); 95 | }); 96 | 97 | metatests.test('runIf do not run asyncFn', (test) => { 98 | const asyncFn = test.mustNotCall((arg1, arg2, cb) => { 99 | process.nextTick(() => { 100 | cb(null, { arg1, arg2 }); 101 | }); 102 | }); 103 | 104 | metasync.runIf(false, asyncFn, 'val1', 'val2', (err, result) => { 105 | test.error(err); 106 | test.strictSame(result, undefined); 107 | test.end(); 108 | }); 109 | }); 110 | 111 | metatests.test('runIf default value', (test) => { 112 | const asyncFn = test.mustNotCall((val, cb) => { 113 | process.nextTick(() => { 114 | cb(null, val); 115 | }); 116 | }); 117 | 118 | metasync.runIf(false, 'default', asyncFn, 'val', (err, result) => { 119 | test.error(err); 120 | test.strictSame(result, 'default'); 121 | test.end(); 122 | }); 123 | }); 124 | 125 | metatests.test('runIf forward an error', (test) => { 126 | const asyncFn = test.mustCall((cb) => { 127 | process.nextTick(() => { 128 | cb(new Error()); 129 | }); 130 | }); 131 | 132 | metasync.runIf(true, asyncFn, (err) => { 133 | test.isError(err); 134 | test.end(); 135 | }); 136 | }); 137 | 138 | metatests.test('runIfFn', (test) => { 139 | test.plan(5); 140 | const value = 42; 141 | const asyncFn = (cb) => { 142 | cb(null, value); 143 | }; 144 | 145 | metasync.runIfFn(test.mustCall(asyncFn), (err, res) => { 146 | test.error(err); 147 | test.strictSame(res, value); 148 | }); 149 | 150 | metasync.runIfFn(null, (err, res) => { 151 | test.error(err); 152 | test.assertNot(res); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/do.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | // Emulate Asynchronous calls of function 7 | // callback - 8 | const wrapAsync = (callback) => { 9 | setTimeout(callback, Math.floor(Math.random() * 500)); 10 | }; 11 | 12 | metatests.test('simple chain/do', (test) => { 13 | const readConfig = test.mustCall((name, callback) => { 14 | test.strictSame(name, 'myConfig'); 15 | wrapAsync(() => { 16 | callback(null, { name }); 17 | }); 18 | }); 19 | 20 | const selectFromDb = test.mustCall((query, callback) => { 21 | test.strictSame(query, 'select * from cities'); 22 | wrapAsync(() => { 23 | callback(null, [{ name: 'Kiev' }, { name: 'Roma' }]); 24 | }); 25 | }); 26 | 27 | const getHttpPage = test.mustCall((url, callback) => { 28 | test.strictSame(url, 'http://kpi.ua'); 29 | wrapAsync(() => { 30 | callback(null, 'Some archaic web here'); 31 | }); 32 | }); 33 | 34 | const readFile = test.mustCall((path, callback) => { 35 | test.strictSame(path, 'README.md'); 36 | wrapAsync(() => { 37 | callback(null, 'file content'); 38 | }); 39 | }); 40 | 41 | const c1 = metasync 42 | .do(readConfig, 'myConfig') 43 | .do(selectFromDb, 'select * from cities') 44 | .do(getHttpPage, 'http://kpi.ua') 45 | .do(readFile, 'README.md'); 46 | 47 | c1((err, result) => { 48 | test.error(err); 49 | test.strictSame(result, 'file content'); 50 | test.end(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/examples.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const fs = require('fs'); 5 | const metatests = require('metatests'); 6 | const path = require('path'); 7 | const ASYNC_TIMEOUT = 200; 8 | 9 | // Data Collector 10 | 11 | metatests.test('dataCollector / simple', (test) => { 12 | const dataCollector = metasync.collect(4).done((err, data) => { 13 | test.error(err); 14 | test.strictSame(Object.keys(data).length, 4); 15 | test.end(); 16 | }); 17 | 18 | dataCollector.collect('user', null, { name: 'Marcus Aurelius' }); 19 | 20 | fs.readFile(path.join(__dirname, '..', 'README.md'), (err, data) => { 21 | dataCollector.collect('readme', err, data); 22 | }); 23 | 24 | fs.readFile(path.join(__dirname, '..', 'AUTHORS'), (err, data) => { 25 | dataCollector.collect('authors', err, data); 26 | }); 27 | 28 | setTimeout(() => { 29 | dataCollector.pick('timer', { date: new Date() }); 30 | }, ASYNC_TIMEOUT); 31 | }); 32 | 33 | metatests.test('data collector / timeout', (test) => { 34 | const expectedErr = new Error('Metasync: Collector timed out'); 35 | const expectedData = { user: { name: 'Marcus Aurelius' } }; 36 | 37 | const dataCollector = metasync 38 | .collect(4) 39 | .timeout(1000) 40 | .done((err, data) => { 41 | test.isError(err, expectedErr); 42 | test.strictSame(data, expectedData); 43 | test.end(); 44 | }); 45 | 46 | dataCollector.pick('user', { name: 'Marcus Aurelius' }); 47 | }); 48 | 49 | metatests.test('data collector / error', (test) => { 50 | const expectedErr = new Error('User not found'); 51 | const expectedData = { file: 'file content' }; 52 | 53 | const dataCollector = metasync.collect(4).done((err, data) => { 54 | test.isError(err, expectedErr); 55 | test.strictSame(data, expectedData); 56 | test.end(); 57 | }); 58 | 59 | dataCollector.pick('file', 'file content'); 60 | dataCollector.fail('user', new Error('User not found')); 61 | dataCollector.pick('score', 1000); 62 | dataCollector.fail('tcp', new Error('No socket')); 63 | }); 64 | 65 | // Key Collector 66 | 67 | metatests.test('key collector / simple', (test) => { 68 | const keyCollector = metasync 69 | .collect(['user', 'readme']) 70 | .timeout(2000) 71 | .done((err, data) => { 72 | test.error(err); 73 | test.strictSame(Object.keys(data).length, 2); 74 | test.end(); 75 | }); 76 | 77 | keyCollector.pick('user', { name: 'Marcus Aurelius' }); 78 | 79 | fs.readFile(path.join(__dirname, '..', 'README.md'), (err, data) => { 80 | test.error(err); 81 | keyCollector.pick('readme', data); 82 | }); 83 | }); 84 | 85 | // Parallel execution 86 | 87 | metatests.test('parallel', (test) => { 88 | test.plan(5); 89 | 90 | const expectedData = { 91 | data1: 'result1', 92 | data2: 'result2', 93 | data3: 'result3', 94 | arg: 'arg', 95 | }; 96 | 97 | const pf1 = (data, callback) => { 98 | test.pass('must call'); 99 | setTimeout(() => callback(null, { data1: 'result1' }), ASYNC_TIMEOUT); 100 | }; 101 | 102 | const pf2 = (data, callback) => { 103 | test.pass('must call'); 104 | setTimeout(() => callback(null, { data2: 'result2' }), ASYNC_TIMEOUT); 105 | }; 106 | 107 | const pf3 = (data, callback) => { 108 | test.pass('must call'); 109 | setTimeout(() => callback(null, { data3: 'result3' }), ASYNC_TIMEOUT); 110 | }; 111 | 112 | metasync.parallel([pf1, pf2, pf3], { arg: 'arg' }, (err, data) => { 113 | test.error(err); 114 | test.strictSame(data, expectedData); 115 | }); 116 | }); 117 | 118 | // Sequential execution 119 | 120 | metatests.test('sequential', (test) => { 121 | test.plan(5); 122 | 123 | const sf1 = (data, callback) => { 124 | test.strictSame(data, ['arg']); 125 | setTimeout(() => callback(null, 'result1'), ASYNC_TIMEOUT); 126 | }; 127 | 128 | const sf2 = (data, callback) => { 129 | test.pass('must call'); 130 | setTimeout(() => callback(null, 'result2'), ASYNC_TIMEOUT); 131 | }; 132 | 133 | const sf3 = (data, callback) => { 134 | test.strictSame(data, ['arg', 'result1', 'result2']); 135 | setTimeout(() => callback(null, 'result3'), ASYNC_TIMEOUT); 136 | }; 137 | 138 | metasync.sequential([sf1, sf2, sf3], ['arg'], (err, data) => { 139 | test.error(err); 140 | test.strictSame(data, ['arg', 'result1', 'result2', 'result3']); 141 | }); 142 | }); 143 | 144 | // Asynchrous filter 145 | 146 | metatests.test('asynchronus filter', (test) => { 147 | const dataToFilter = [ 148 | 'Lorem', 149 | 'ipsum', 150 | 'dolor', 151 | 'sit', 152 | 'amet', 153 | 'consectetur', 154 | 'adipiscing', 155 | 'elit', 156 | 'sed', 157 | 'do', 158 | 'eiusmod', 159 | 'tempor', 160 | 'incididunt', 161 | 'ut', 162 | 'labore', 163 | 'et', 164 | 'dolore', 165 | 'magna', 166 | 'aliqua', 167 | ]; 168 | 169 | const expectedResult = [ 170 | 'Lorem', 171 | 'ipsum', 172 | 'sit', 173 | 'amet', 174 | 'elit', 175 | 'sed', 176 | 'do', 177 | 'eiusmod', 178 | 'tempor', 179 | 'ut', 180 | 'labore', 181 | 'et', 182 | ]; 183 | 184 | const filterPredicate = (item, callback) => { 185 | // filter words which consists of unique letters only 186 | const letters = []; 187 | for (let i = 0; i < item.length; ++i) { 188 | if (letters.includes(item[i].toLowerCase())) break; 189 | letters.push(item[i].toLowerCase()); 190 | } 191 | 192 | setTimeout( 193 | () => callback(null, letters.length === item.length), 194 | ASYNC_TIMEOUT, 195 | ); 196 | }; 197 | 198 | metasync.filter(dataToFilter, filterPredicate, (err, result) => { 199 | test.error(err); 200 | test.strictSame(result, expectedResult); 201 | test.end(); 202 | }); 203 | }); 204 | 205 | // Asynchrous find 206 | 207 | metatests.test('asynchronus find', (test) => { 208 | metasync.find( 209 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 210 | (item, callback) => callback(null, item % 3 === 0 && item % 5 === 0), 211 | (err, result) => { 212 | test.error(err); 213 | test.strictSame(result, 15); 214 | test.end(); 215 | }, 216 | ); 217 | }); 218 | 219 | // Asynchrous some 220 | metatests.test('asynchronus some', (test) => { 221 | metasync.some( 222 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 223 | (item, callback) => { 224 | const res = item % 3 === 0 && item % 5 === 0; 225 | callback(null, res); 226 | }, 227 | (err, result) => { 228 | test.error(err); 229 | test.strictSame(result, true); 230 | test.end(); 231 | }, 232 | ); 233 | }); 234 | 235 | // Asyncronous each in parallel 236 | 237 | metatests.test('asyncronous each', (test) => { 238 | const result = {}; 239 | metasync.each( 240 | ['a', 'b', 'c'], 241 | (item, callback) => { 242 | result[item] = item; 243 | callback(null); 244 | }, 245 | (err) => { 246 | test.error(err); 247 | test.strictSame(result, { a: 'a', b: 'b', c: 'c' }); 248 | test.end(); 249 | }, 250 | ); 251 | }); 252 | 253 | // Asyncronous series (sequential) 254 | 255 | metatests.test('asynchronus series', (test) => { 256 | const result = []; 257 | metasync.series( 258 | ['a', 'b', 'c', 'd'], 259 | (item, callback) => { 260 | result.push(item.toUpperCase()); 261 | callback(null); 262 | }, 263 | (err) => { 264 | test.error(err); 265 | test.strictSame(result, ['A', 'B', 'C', 'D']); 266 | test.end(); 267 | }, 268 | ); 269 | }); 270 | 271 | // Asyncronous reduce (sequential) 272 | 273 | metatests.test('asynchronus reduce', (test) => { 274 | metasync.reduce( 275 | ['a', 'b', 'c', 'd'], 276 | (prev, curr, callback) => { 277 | callback(null, prev + curr); 278 | }, 279 | (err, data) => { 280 | test.error(err); 281 | test.strictSame(data, 'abcd'); 282 | test.end(); 283 | }, 284 | ); 285 | }); 286 | 287 | // Queue 288 | 289 | metatests.test('queue / simple', (test) => { 290 | const expectedResult = [1, 2, 3, 4, 5, 6, 8, 9]; 291 | const result = []; 292 | 293 | const queue = metasync 294 | .queue(3) 295 | .process((item, callback) => { 296 | result.push(item.id); 297 | setTimeout(callback, 100); 298 | }) 299 | .drain(() => { 300 | test.strictSame(result, expectedResult); 301 | test.end(); 302 | }); 303 | 304 | queue.add({ id: 1 }); 305 | queue.add({ id: 2 }); 306 | queue.add({ id: 3 }); 307 | queue.add({ id: 4 }); 308 | queue.add({ id: 5 }); 309 | queue.add({ id: 6 }); 310 | queue.add({ id: 8 }); 311 | queue.add({ id: 9 }); 312 | }); 313 | 314 | metatests.test('queue / pause', (test) => { 315 | const expectedResult = [1, 3, 4, 7, 8, 9]; 316 | const result = []; 317 | 318 | const queue = metasync 319 | .queue(3) 320 | .process((item, callback) => { 321 | result.push(item.id); 322 | setTimeout(callback, 100); 323 | }) 324 | .drain(() => { 325 | test.strictSame(result, expectedResult); 326 | test.end(); 327 | }); 328 | 329 | queue.add({ id: 1 }); 330 | queue.pause(); 331 | 332 | queue.add({ id: 2 }); 333 | 334 | queue.resume(); 335 | 336 | queue.add({ id: 3 }); 337 | queue.add({ id: 4 }); 338 | queue.add({ id: 5 }); 339 | queue.add({ id: 6 }); 340 | 341 | queue.clear(); 342 | 343 | queue.add({ id: 7 }); 344 | queue.add({ id: 8 }); 345 | queue.add({ id: 9 }); 346 | }); 347 | 348 | // Trottle 349 | 350 | metatests.test('trottle', (test) => { 351 | const expectedResult = ['A', 'E', 'F', 'I']; 352 | const result = []; 353 | let state; 354 | 355 | const fn = () => { 356 | result.push(state); 357 | }; 358 | 359 | const f1 = metasync.throttle(500, fn); 360 | 361 | // to be called 2 times (first and last: A and E) 362 | state = 'A'; 363 | f1(); 364 | state = 'B'; 365 | f1(); 366 | state = 'C'; 367 | f1(); 368 | state = 'D'; 369 | f1(); 370 | state = 'E'; 371 | f1(); 372 | 373 | // to be called 2 times (last will be I) 374 | setTimeout(() => { 375 | state = 'F'; 376 | f1(); 377 | }, 600); 378 | setTimeout(() => { 379 | state = 'G'; 380 | f1(); 381 | }, 700); 382 | setTimeout(() => { 383 | state = 'H'; 384 | f1(); 385 | }, 1000); 386 | setTimeout(() => { 387 | state = 'I'; 388 | f1(); 389 | }, 1100); 390 | 391 | setTimeout(() => { 392 | test.strictSame(result, expectedResult); 393 | test.end(); 394 | }, 3000); 395 | }); 396 | 397 | // Debounce 398 | 399 | metatests.test('debounce', (test) => { 400 | const expectedResult = ['E', 'I']; 401 | const result = []; 402 | let state; 403 | 404 | const fn = () => { 405 | result.push(state); 406 | }; 407 | 408 | const f1 = metasync.debounce(500, fn, ['I']); 409 | 410 | // to be called one time (E) 411 | state = 'A'; 412 | f1(); 413 | state = 'B'; 414 | f1(); 415 | state = 'C'; 416 | f1(); 417 | state = 'D'; 418 | f1(); 419 | state = 'E'; 420 | f1(); 421 | 422 | // to be called one time (I) 423 | setTimeout(() => { 424 | state = 'F'; 425 | f1(); 426 | }, 600); 427 | setTimeout(() => { 428 | state = 'G'; 429 | f1(); 430 | }, 700); 431 | setTimeout(() => { 432 | state = 'H'; 433 | f1(); 434 | }, 1000); 435 | setTimeout(() => { 436 | state = 'I'; 437 | f1(); 438 | }, 1100); 439 | 440 | setTimeout(() => { 441 | test.strictSame(result, expectedResult); 442 | test.end(); 443 | }, 3000); 444 | }); 445 | 446 | // Map 447 | 448 | metatests.test('asynchronus map / simple', (test) => { 449 | metasync.map( 450 | [1, 2, 3], 451 | (item, callback) => { 452 | setTimeout(() => { 453 | callback(null, item * item); 454 | }, item * 10); 455 | }, 456 | (error, result) => { 457 | test.error(error); 458 | test.strictSame(result, [1, 4, 9]); 459 | test.end(); 460 | }, 461 | ); 462 | }); 463 | 464 | metatests.test('asynchronus map / error', (test) => { 465 | metasync.map( 466 | [1, 2, 3], 467 | (item, callback) => { 468 | setTimeout(() => { 469 | if (item === 2) { 470 | callback(new Error()); 471 | } else { 472 | callback(null, item); 473 | } 474 | }, item * 10); 475 | }, 476 | (error, result) => { 477 | test.isError(error); 478 | test.strictSame(result, undefined); 479 | test.end(); 480 | }, 481 | ); 482 | }); 483 | 484 | // Timeout 485 | 486 | metatests.test('timeout', (test) => { 487 | metasync.timeout( 488 | 200, 489 | (done) => { 490 | setTimeout(done, 300); 491 | }, 492 | (err) => { 493 | const expectedErr = new Error( 494 | 'Metasync: asynchronous function timed out', 495 | ); 496 | test.isError(err, expectedErr); 497 | test.end(); 498 | }, 499 | ); 500 | }); 501 | -------------------------------------------------------------------------------- /test/firstOf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('firstOf', (test) => { 7 | const returningFnIndex = 2; 8 | let dataReturned = false; 9 | 10 | const execUnlessDataReturned = (data) => (callback) => { 11 | if (dataReturned) { 12 | callback(null, data); 13 | } else { 14 | process.nextTick(execUnlessDataReturned); 15 | } 16 | }; 17 | const makeIFn = (i) => (callback) => 18 | process.nextTick(() => { 19 | const iData = 'data' + i; 20 | if (i === returningFnIndex) { 21 | dataReturned = true; 22 | callback(null, iData); 23 | } else { 24 | execUnlessDataReturned(iData); 25 | } 26 | }); 27 | 28 | const fns = [1, 2, 3].map(makeIFn); 29 | 30 | metasync.firstOf(fns, (err, data) => { 31 | test.error(err); 32 | test.strictSame(data, 'data2'); 33 | test.end(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/fp.ap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | const asyncArgs = (callback) => process.nextTick(() => callback(null, 4, 5)); 7 | const functionInCallback = (callback) => 8 | process.nextTick(() => callback(null, (x, y) => x + y)); 9 | const asyncError = new Error('Async error'); 10 | const asyncErrorCb = (callback) => process.nextTick(() => callback(asyncError)); 11 | 12 | metatests.test('two successful functions', (test) => { 13 | metasync.ap( 14 | asyncArgs, 15 | functionInCallback, 16 | )((err, res) => { 17 | test.error(err); 18 | test.strictSame(res, 9); 19 | test.end(); 20 | }); 21 | }); 22 | 23 | metatests.test('first function with error', (test) => { 24 | metasync.ap( 25 | asyncErrorCb, 26 | functionInCallback, 27 | )((err, res) => { 28 | test.strictSame(err, asyncError); 29 | test.strictSame(res, undefined); 30 | test.end(); 31 | }); 32 | }); 33 | 34 | metatests.test('second function with error', (test) => { 35 | metasync.ap( 36 | asyncArgs, 37 | asyncErrorCb, 38 | )((err, res) => { 39 | test.strictSame(err, asyncError); 40 | test.strictSame(res, undefined); 41 | test.end(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/fp.asAsync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | const asyncSum = (x, y, callback) => 7 | process.nextTick(() => callback(null, x + y)); 8 | const tripleFnInCb = (callback) => 9 | process.nextTick(() => callback(null, (x) => x * 3)); 10 | const asyncMultBy11 = (x, callback) => 11 | process.nextTick(() => callback(null, x * 11)); 12 | 13 | metatests.test('asAsync all functions test', (test) => { 14 | const done = (err, res) => { 15 | test.error(err); 16 | test.strictSame(res, 1848); 17 | test.end(); 18 | }; 19 | metasync 20 | .asAsync(asyncSum, 3, 5) 21 | .fmap((x) => x * 7) 22 | .ap(tripleFnInCb) 23 | .concat(asyncMultBy11)(done); 24 | }); 25 | -------------------------------------------------------------------------------- /test/fp.concat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | const asyncData = 'data'; 7 | const asyncDataCb = (callback) => 8 | process.nextTick(() => callback(null, asyncData)); 9 | const asyncTwice = (str, callback) => 10 | process.nextTick(() => callback(null, str + str)); 11 | const asyncError = new Error('Async error'); 12 | const asyncErrorCb = (callback) => process.nextTick(() => callback(asyncError)); 13 | const asyncTransformErrorCb = (str, callback) => 14 | process.nextTick(() => callback(asyncError)); 15 | 16 | metatests.test('two successful functions', (test) => { 17 | metasync.concat( 18 | asyncDataCb, 19 | asyncTwice, 20 | )((err, res) => { 21 | test.error(err); 22 | test.strictSame(res, 'datadata'); 23 | test.end(); 24 | }); 25 | }); 26 | 27 | metatests.test('first function error', (test) => { 28 | metasync.concat( 29 | asyncErrorCb, 30 | asyncTwice, 31 | )((err, res) => { 32 | test.strictSame(err, asyncError); 33 | test.strictSame(res, undefined); 34 | test.end(); 35 | }); 36 | }); 37 | 38 | metatests.test('second function error', (test) => { 39 | metasync.concat( 40 | asyncDataCb, 41 | asyncTransformErrorCb, 42 | )((err, res) => { 43 | test.strictSame(err, asyncError); 44 | test.strictSame(res, undefined); 45 | test.end(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/fp.fmap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | const asyncData = 'data'; 7 | const asyncDataCb = (callback) => 8 | process.nextTick(() => { 9 | callback(null, asyncData); 10 | }); 11 | const asyncError = new Error('Async error'); 12 | const asyncErrorCb = (callback) => 13 | process.nextTick(() => { 14 | callback(asyncError); 15 | }); 16 | const identity = (x) => x; 17 | const repeatStringTwice = (str) => str + str; 18 | const appendColon = (str) => str + ':'; 19 | const twiceAndColon = (str) => appendColon(repeatStringTwice(str)); 20 | 21 | metatests.test('Result transformation', (test) => { 22 | const expected = 'data:'; 23 | metasync.fmap( 24 | asyncDataCb, 25 | appendColon, 26 | )((err, res) => { 27 | test.error(err); 28 | test.strictSame(expected, res); 29 | test.end(); 30 | }); 31 | }); 32 | 33 | metatests.test('Getting asynchronous error', (test) => { 34 | metasync.fmap( 35 | asyncErrorCb, 36 | appendColon, 37 | )((err, res) => { 38 | test.strictSame(err, asyncError); 39 | test.strictSame(res, undefined); 40 | test.end(); 41 | }); 42 | }); 43 | 44 | const FP1 = 'Getting error with no second argument execution'; 45 | metatests.test(FP1, (test) => { 46 | let executed = false; 47 | metasync.fmap(asyncErrorCb, (str) => { 48 | executed = true; 49 | return appendColon(str); 50 | })(() => { 51 | test.strictSame(executed, false); 52 | test.end(); 53 | }); 54 | }); 55 | 56 | metatests.test('functor law I', (test) => { 57 | metasync.fmap( 58 | asyncDataCb, 59 | identity, 60 | )((err, res) => { 61 | test.error(err); 62 | test.strictSame(asyncData, res); 63 | test.end(); 64 | }); 65 | }); 66 | 67 | metatests.test('functor law II', (test) => { 68 | const fmap = metasync.fmap; 69 | const asyncTwice = fmap(asyncDataCb, repeatStringTwice); 70 | const asyncTwiceAndColon = fmap(asyncTwice, appendColon); 71 | 72 | asyncTwiceAndColon((err1, res1) => { 73 | fmap( 74 | asyncDataCb, 75 | twiceAndColon, 76 | )((err2, res2) => { 77 | test.error(err1); 78 | test.error(err2); 79 | test.strictSame(res1, res2); 80 | test.end(); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/fp.of.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('of test', (test) => { 7 | const args = [1, 2, 3, 4, 5]; 8 | metasync.of(...args)((err, ...argsCb) => { 9 | test.error(err); 10 | test.strictSame(args, argsCb); 11 | test.end(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/memoize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('memoize', (test) => { 7 | const storage = { 8 | file1: Buffer.from('file1'), 9 | file2: Buffer.from('file2'), 10 | }; 11 | 12 | const getData = (file, callback) => { 13 | process.nextTick(() => { 14 | const result = storage[file]; 15 | if (result) callback(null, result); 16 | else callback(new Error('File not found')); 17 | }); 18 | }; 19 | 20 | const memoizedGetData = metasync.memoize(getData); 21 | 22 | const keys = []; 23 | memoizedGetData.on('memoize', (key) => { 24 | keys.push(key); 25 | }); 26 | 27 | memoizedGetData('file1', (err, data) => { 28 | test.error(err); 29 | test.strictSame(data, storage.file1); 30 | memoizedGetData('file2', (err, data) => { 31 | test.error(err); 32 | test.strictSame(data, storage.file2); 33 | memoizedGetData('file1', (err, data) => { 34 | test.error(err); 35 | test.strictSame(data, storage.file1); 36 | memoizedGetData('file2', (err, data) => { 37 | test.error(err); 38 | test.strictSame(data, storage.file2); 39 | test.strictSame(keys, ['file1', 'file2']); 40 | test.end(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | }); 46 | 47 | metatests.test('memoize clear cache', (test) => { 48 | const storage = { 49 | file1: Buffer.from('file1'), 50 | }; 51 | 52 | const getData = (file, callback) => { 53 | process.nextTick(() => { 54 | const result = storage[file]; 55 | if (result) callback(null, result); 56 | else callback(new Error('File not found')); 57 | }); 58 | }; 59 | 60 | const memoizedGetData = metasync.memoize(getData); 61 | 62 | let onClear = false; 63 | memoizedGetData.on('clear', () => { 64 | onClear = true; 65 | }); 66 | 67 | memoizedGetData('file1', (err, data) => { 68 | test.error(err); 69 | test.strictSame(data, storage.file1); 70 | storage.file1 = Buffer.from('changed'); 71 | memoizedGetData.clear(); 72 | memoizedGetData('file1', (err, data) => { 73 | test.error(err); 74 | test.strictSame(data, Buffer.from('changed')); 75 | test.strictSame(onClear, true); 76 | test.end(); 77 | }); 78 | }); 79 | }); 80 | 81 | metatests.test('memoize cache del', (test) => { 82 | const storage = { 83 | file1: Buffer.from('file1'), 84 | }; 85 | 86 | const getData = (file, callback) => { 87 | process.nextTick(() => { 88 | const result = storage[file]; 89 | if (result) callback(null, result); 90 | else callback(new Error('File not found')); 91 | }); 92 | }; 93 | 94 | const memoizedGetData = metasync.memoize(getData); 95 | 96 | let onDel = false; 97 | memoizedGetData.on('del', () => { 98 | onDel = true; 99 | }); 100 | 101 | memoizedGetData('file1', (err, data) => { 102 | test.error(err); 103 | test.strictSame(data, storage.file1); 104 | storage.file1 = Buffer.from('changed'); 105 | memoizedGetData.del('file1'); 106 | memoizedGetData('file1', (err, data) => { 107 | test.error(err); 108 | test.strictSame(data, Buffer.from('changed')); 109 | test.strictSame(onDel, true); 110 | test.end(); 111 | }); 112 | }); 113 | }); 114 | 115 | metatests.test('memoize cache add', (test) => { 116 | const getData = (file, callback) => { 117 | process.nextTick(() => { 118 | const result = Buffer.from('added'); 119 | callback(null, result); 120 | }); 121 | }; 122 | 123 | const memoizedGetData = metasync.memoize(getData); 124 | 125 | let onAdd = false; 126 | memoizedGetData.on('add', () => { 127 | onAdd = true; 128 | }); 129 | 130 | const file1 = Buffer.from('added'); 131 | memoizedGetData.add('file1', null, file1); 132 | memoizedGetData('file1', (err, data) => { 133 | test.error(err); 134 | test.strictSame(data, Buffer.from('added')); 135 | test.strictSame(onAdd, true); 136 | test.end(); 137 | }); 138 | }); 139 | 140 | metatests.test('memoize cache get', (test) => { 141 | const getData = (file, callback) => { 142 | process.nextTick(() => { 143 | const result = Buffer.from('added'); 144 | callback(null, result); 145 | }); 146 | }; 147 | 148 | const memoizedGetData = metasync.memoize(getData); 149 | 150 | const file1 = Buffer.from('added'); 151 | memoizedGetData.add('file1', null, file1); 152 | memoizedGetData.get('file1', (err, data) => { 153 | test.error(err); 154 | test.strictSame(data, Buffer.from('added')); 155 | test.end(); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /test/poolify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('poolify simple', (test) => { 7 | const buffer = () => new Uint32Array(128); 8 | 9 | const pool = metasync.poolify(buffer, 10, 15, 20); 10 | 11 | pool((item1) => { 12 | test.strictSame(pool.items.length, 14); 13 | test.strictSame(item1.length, 128); 14 | pool((item2) => { 15 | test.strictSame(pool.items.length, 13); 16 | test.strictSame(item2.length, 128); 17 | pool([item1]); 18 | pool([item2]); 19 | test.strictSame(pool.items.length, 15); 20 | test.end(); 21 | }); 22 | }); 23 | }); 24 | 25 | metatests.test('poolify loop', (test) => { 26 | const buffer = () => new Uint32Array(128); 27 | 28 | const pool = metasync.poolify(buffer, 10, 15, 20); 29 | 30 | for (let i = 0; i < 15; i++) { 31 | pool((item) => { 32 | pool([item]); 33 | if (i === 14) { 34 | test.strictSame(item.length, 128); 35 | test.end(); 36 | } 37 | }); 38 | } 39 | }); 40 | 41 | metatests.test('poolify max', (test) => { 42 | const buffer = () => new Uint32Array(128); 43 | 44 | const pool = metasync.poolify(buffer, 5, 7, 10); 45 | 46 | for (let i = 0; i < 15; i++) { 47 | pool((item) => { 48 | setTimeout(() => { 49 | pool([item]); 50 | if (i === 14) { 51 | test.end(); 52 | } 53 | }, 100); 54 | }); 55 | } 56 | }); 57 | 58 | metatests.test('poolify delayed order', (test) => { 59 | const buffer = () => new Uint32Array(128); 60 | 61 | const pool = metasync.poolify(buffer, 0, 2, 2); 62 | 63 | let get3 = false; 64 | pool((item1) => { 65 | test.strictSame(pool.items.length, 1); 66 | pool((item2) => { 67 | test.strictSame(pool.items.length, 0); 68 | pool((item3) => { 69 | test.strictSame(pool.items.length, 0); 70 | test.strictSame(get3, false); 71 | get3 = true; 72 | pool([item3]); 73 | }); 74 | pool((item4) => { 75 | test.strictSame(pool.items.length, 1); 76 | test.strictSame(get3, true); 77 | pool([item4]); 78 | test.end(); 79 | }); 80 | pool([item1]); 81 | pool([item2]); 82 | }); 83 | }); 84 | }); 85 | 86 | metatests.test('poolify functor', (test) => { 87 | const adder = (a) => (b) => adder(a + b); 88 | 89 | const pool = metasync.poolify(adder, 1, 2, 3); 90 | 91 | pool((item1) => { 92 | test.strictSame(pool.items.length, 1); 93 | pool((item2) => { 94 | test.strictSame(pool.items.length, 0); 95 | pool([item1]); 96 | pool([item2]); 97 | test.strictSame(pool.items.length, 2); 98 | test.end(); 99 | }); 100 | }); 101 | }); 102 | 103 | metatests.test('poolify get sync', (test) => { 104 | const adder = (a) => (b) => adder(a + b); 105 | 106 | const pool = metasync.poolify(adder, 1, 2, 3); 107 | 108 | const item1 = pool(); 109 | test.strictSame(pool.items.length, 1); 110 | const item2 = pool(); 111 | test.strictSame(pool.items.length, 0); 112 | const item3 = pool(); 113 | test.strictSame(pool.items.length, 0); 114 | const item4 = pool(); 115 | test.strictSame(item4, undefined); 116 | test.strictSame(pool.items.length, 0); 117 | pool([item1]); 118 | pool([item2]); 119 | pool([item3]); 120 | test.strictSame(pool.items.length, 3); 121 | test.end(); 122 | }); 123 | -------------------------------------------------------------------------------- /test/queue.both.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('priority / roundRobin', (test) => { 7 | const expectedResult = [ 8 | 1, 2, 3, 8, 17, 13, 7, 4, 21, 12, 16, 11, 20, 15, 19, 10, 23, 14, 18, 9, 22, 9 | 6, 5, 10 | ]; 11 | const result = []; 12 | 13 | const q = metasync 14 | .queue(3) 15 | .roundRobin() 16 | .priority() 17 | .process((item, cb) => { 18 | result.push(item.id); 19 | setTimeout(cb, 100); 20 | }); 21 | 22 | q.drain(() => { 23 | test.strictSame(result, expectedResult); 24 | test.end(); 25 | }); 26 | 27 | q.add({ id: 1 }, 1, 10); 28 | q.add({ id: 2 }, 2, 10); 29 | q.add({ id: 3 }, 3, 10); 30 | q.add({ id: 4 }, 4, 20); 31 | q.add({ id: 5 }, 1, 10); 32 | q.add({ id: 6 }, 2, 10); 33 | q.add({ id: 7 }, 3, 10); 34 | q.add({ id: 8 }, 4, 50); 35 | q.add({ id: 9 }, 2, 50); 36 | q.add({ id: 10 }, 2, 60); 37 | q.add({ id: 11 }, 2, 70); 38 | q.add({ id: 12 }, 2, 80); 39 | q.add({ id: 13 }, 2, 90); 40 | q.add({ id: 14 }, 2, 60); 41 | q.add({ id: 15 }, 2, 70); 42 | q.add({ id: 16 }, 1, 80); 43 | q.add({ id: 17 }, 1, 90); 44 | q.add({ id: 18 }, 1, 60); 45 | q.add({ id: 19 }, 1, 70); 46 | q.add({ id: 20 }, 1, 80); 47 | q.add({ id: 21 }, 1, 90); 48 | q.add({ id: 22 }, 1, 60); 49 | q.add({ id: 23 }, 1, 70); 50 | }); 51 | -------------------------------------------------------------------------------- /test/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('queue add', (test) => { 7 | const queue = metasync.queue(3).timeout(2000); 8 | let taskIndex = 1; 9 | 10 | queue.process((item, callback) => { 11 | process.nextTick(() => { 12 | test.strictSame(item, { id: taskIndex }); 13 | taskIndex++; 14 | callback(null); 15 | }); 16 | }); 17 | 18 | queue.drain(() => { 19 | test.end(); 20 | }); 21 | 22 | let id; 23 | for (id = 1; id < 10; id++) { 24 | queue.add({ id }); 25 | } 26 | }); 27 | 28 | metatests.test('queue pause resume clear', (test) => { 29 | const queue = metasync.queue(3); 30 | queue.pause(); 31 | queue.add({ id: 1 }); 32 | test.strictSame(queue.count, 0); 33 | 34 | let itemIsProcessed = false; 35 | queue.process((item, callback) => { 36 | itemIsProcessed = true; 37 | callback(null); 38 | }); 39 | 40 | queue.add({ id: 2 }); 41 | test.strictSame(itemIsProcessed, false); 42 | 43 | queue.resume(); 44 | test.strictSame(queue.paused, false); 45 | 46 | queue.clear(); 47 | test.strictSame(queue.count, 0); 48 | test.end(); 49 | }); 50 | 51 | metatests.test('queue with no process function and no timeout', (test) => { 52 | const queue = metasync.queue(3); 53 | queue.add({ id: 1 }); 54 | queue.add({ id: 2 }); 55 | queue.add({ id: 3 }); 56 | 57 | test.strictSame(queue.count, 0); 58 | test.end(); 59 | }); 60 | 61 | metatests.test('queue with timeout event', (test) => { 62 | const timeoutErr = new Error('Metasync: Queue timed out'); 63 | 64 | const queue = metasync.queue(3); 65 | 66 | queue.process((item, callback) => { 67 | setTimeout(() => { 68 | callback(null, item); 69 | }, 1000); 70 | }); 71 | 72 | queue.timeout(1, (err, res) => { 73 | test.strictSame(err, timeoutErr); 74 | test.strictSame(res, undefined); 75 | test.end(); 76 | }); 77 | 78 | queue.add({ id: 1 }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/queue.lifo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('lifo / simple', (test) => { 7 | const expectedResult = [1, 2, 3, 9, 8, 7, 6, 5, 4]; 8 | const result = []; 9 | 10 | const q = metasync 11 | .queue(3) 12 | .priority() 13 | .lifo() 14 | .process((item, cb) => { 15 | result.push(item.id); 16 | setTimeout(cb, 100); 17 | }); 18 | 19 | q.drain(() => { 20 | test.strictSame(result, expectedResult); 21 | test.end(); 22 | }); 23 | 24 | q.add({ id: 1 }); 25 | q.add({ id: 2 }); 26 | q.add({ id: 3 }); 27 | q.add({ id: 4 }); 28 | q.add({ id: 5 }); 29 | q.add({ id: 6 }); 30 | q.add({ id: 7 }); 31 | q.add({ id: 8 }); 32 | q.add({ id: 9 }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/queue.modes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('queue default FIFO', (test) => { 7 | const queue = metasync.queue(3).timeout(1); 8 | const res = []; 9 | 10 | queue.process((item, callback) => { 11 | process.nextTick(() => { 12 | res.push(item.id); 13 | callback(); 14 | }); 15 | }); 16 | 17 | queue.drain(() => { 18 | test.strictSame(res, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 19 | test.end(); 20 | }); 21 | 22 | for (let id = 1; id < 10; id++) { 23 | queue.add({ id }); 24 | } 25 | }); 26 | 27 | metatests.test('queue FIFO', (test) => { 28 | const queue = metasync.queue(3).fifo().timeout(1); 29 | const res = []; 30 | 31 | queue.process((item, callback) => { 32 | process.nextTick(() => { 33 | res.push(item.id); 34 | callback(); 35 | }); 36 | }); 37 | 38 | queue.drain(() => { 39 | test.strictSame(res, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 40 | test.end(); 41 | }); 42 | 43 | for (let id = 1; id < 10; id++) { 44 | queue.add({ id }); 45 | } 46 | }); 47 | 48 | metatests.test('queue LIFO', (test) => { 49 | const queue = metasync.queue(3).lifo().timeout(1); 50 | const res = []; 51 | 52 | queue.process((item, callback) => { 53 | process.nextTick(() => { 54 | res.push(item.id); 55 | callback(); 56 | }); 57 | }); 58 | 59 | queue.drain(() => { 60 | test.strictSame(res, [1, 2, 3, 9, 8, 7, 6, 5, 4]); 61 | test.end(); 62 | }); 63 | 64 | for (let id = 1; id < 10; id++) { 65 | queue.add({ id }); 66 | } 67 | }); 68 | 69 | metatests.test('queue priority', (test) => { 70 | const queue = metasync.queue(3).priority(); 71 | const res = []; 72 | 73 | queue.process((item, callback) => { 74 | process.nextTick(() => { 75 | res.push(item.id); 76 | callback(); 77 | }); 78 | }); 79 | 80 | queue.drain(() => { 81 | test.strictSame(res, [1, 2, 3, 8, 5, 4, 7, 9, 6]); 82 | test.end(); 83 | }); 84 | 85 | queue.add({ id: 1 }, 1); 86 | queue.add({ id: 2 }, 4); 87 | queue.add({ id: 3 }, 5); 88 | queue.add({ id: 4 }, 7); 89 | queue.add({ id: 5 }, 8); 90 | queue.add({ id: 6 }, 2); 91 | queue.add({ id: 7 }, 6); 92 | queue.add({ id: 8 }, 9); 93 | queue.add({ id: 9 }, 3); 94 | }); 95 | 96 | metatests.test('queue round robin', (test) => { 97 | const queue = metasync.queue(3).roundRobin(); 98 | const res = []; 99 | 100 | queue.process((item, callback) => { 101 | process.nextTick(() => { 102 | res.push(item.id); 103 | callback(); 104 | }); 105 | }); 106 | 107 | queue.drain(() => { 108 | test.strictSame(res, [1, 2, 3, 4, 7, 5, 8, 6, 9]); 109 | test.end(); 110 | }); 111 | 112 | queue.add({ id: 1 }, 5); 113 | queue.add({ id: 2 }, 5); 114 | queue.add({ id: 3 }, 5); 115 | queue.add({ id: 4 }, 5); 116 | queue.add({ id: 5 }, 5); 117 | queue.add({ id: 6 }, 5); 118 | queue.add({ id: 7 }, 2); 119 | queue.add({ id: 8 }, 2); 120 | queue.add({ id: 9 }, 2); 121 | }); 122 | 123 | metatests.test('queue round robin with priority', (test) => { 124 | const queue = metasync.queue(3).roundRobin().priority(); 125 | const res = []; 126 | 127 | queue.process((item, callback) => { 128 | process.nextTick(() => { 129 | res.push(item.id); 130 | callback(); 131 | }); 132 | }); 133 | 134 | queue.drain(() => { 135 | test.strictSame(res, [1, 2, 3, 4, 8, 9, 7, 5, 6]); 136 | test.end(); 137 | }); 138 | 139 | queue.add({ id: 1 }, 1, 0); 140 | queue.add({ id: 2 }, 1, 0); 141 | queue.add({ id: 3 }, 1, 10); 142 | queue.add({ id: 4 }, 1, 20); 143 | queue.add({ id: 5 }, 2, 0); 144 | queue.add({ id: 6 }, 2, 0); 145 | queue.add({ id: 7 }, 2, 10); 146 | queue.add({ id: 8 }, 2, 20); 147 | queue.add({ id: 9 }, 3, 0); 148 | }); 149 | -------------------------------------------------------------------------------- /test/queue.pipe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('priority / pipe', (test) => { 7 | const expectedResult = [2, 8, 6, 4]; 8 | const result = []; 9 | 10 | const q1 = metasync 11 | .queue(3) 12 | .priority() 13 | .process((item, cb) => { 14 | setTimeout(cb, 50, item.id % 2 ? new Error('unexpected') : null, item); 15 | }); 16 | 17 | const q2 = metasync 18 | .queue(1) 19 | .wait(100) 20 | .timeout(200) 21 | .priority() 22 | .process((item, cb) => { 23 | result.push(item.id); 24 | setTimeout(cb, 90); 25 | }); 26 | 27 | q1.pipe(q2); 28 | 29 | q2.drain(() => { 30 | test.strictSame(result, expectedResult); 31 | test.end(); 32 | }); 33 | 34 | q1.add({ id: 1 }, 0); 35 | q1.add({ id: 2 }, 0); 36 | q1.add({ id: 3 }, 1); 37 | q1.add({ id: 4 }, 0); 38 | q1.add({ id: 5 }, 0); 39 | q1.add({ id: 6 }, 10); 40 | q1.add({ id: 7 }, 0); 41 | q1.add({ id: 8 }, 100); 42 | q1.add({ id: 9 }, 0); 43 | }); 44 | -------------------------------------------------------------------------------- /test/queue.priority.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('fifo / priority', (test) => { 7 | const expectedResult = [1, 2, 3, 8, 6, 4, 5, 7, 9]; 8 | const result = []; 9 | 10 | const q = metasync 11 | .queue(3) 12 | .priority() 13 | .process((item, cb) => { 14 | result.push(item.id); 15 | setTimeout(cb, 100); 16 | }); 17 | 18 | q.drain(() => { 19 | test.strictSame(result, expectedResult); 20 | test.end(); 21 | }); 22 | 23 | q.add({ id: 1 }, 0); 24 | q.add({ id: 2 }, 0); 25 | q.add({ id: 3 }, 1); 26 | q.add({ id: 4 }, 0); 27 | q.add({ id: 5 }, 0); 28 | q.add({ id: 6 }, 10); 29 | q.add({ id: 7 }, 0); 30 | q.add({ id: 8 }, 100); 31 | q.add({ id: 9 }, 0); 32 | }); 33 | -------------------------------------------------------------------------------- /test/queue.roundRobin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('roundRobin', (test) => { 7 | const expectedResult = [ 8 | 1, 2, 3, 4, 5, 6, 7, 8, 20, 9, 21, 10, 22, 11, 23, 12, 13, 14, 15, 16, 17, 9 | 18, 19, 10 | ]; 11 | const result = []; 12 | 13 | const q = metasync 14 | .queue(3) 15 | .roundRobin() 16 | .process((item, cb) => { 17 | result.push(item.id); 18 | setTimeout(cb, 100); 19 | }); 20 | 21 | q.drain(() => { 22 | test.strictSame(result, expectedResult); 23 | test.end(); 24 | }); 25 | q.add({ id: 1 }, 1); 26 | q.add({ id: 2 }, 2); 27 | q.add({ id: 3 }, 3); 28 | q.add({ id: 4 }, 4); 29 | q.add({ id: 5 }, 1); 30 | q.add({ id: 6 }, 2); 31 | q.add({ id: 7 }, 3); 32 | q.add({ id: 8 }, 4); 33 | q.add({ id: 9 }, 2); 34 | q.add({ id: 10 }, 2); 35 | q.add({ id: 11 }, 2); 36 | q.add({ id: 12 }, 2); 37 | q.add({ id: 13 }, 2); 38 | q.add({ id: 14 }, 2); 39 | q.add({ id: 15 }, 2); 40 | q.add({ id: 16 }, 2); 41 | q.add({ id: 17 }, 2); 42 | q.add({ id: 18 }, 2); 43 | q.add({ id: 19 }, 2); 44 | q.add({ id: 20 }, 1); 45 | q.add({ id: 21 }, 1); 46 | q.add({ id: 22 }, 1); 47 | q.add({ id: 23 }, 1); 48 | }); 49 | -------------------------------------------------------------------------------- /test/throttle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const metasync = require('..'); 4 | const metatests = require('metatests'); 5 | 6 | metatests.test('throttle', (test) => { 7 | let callCount = 0; 8 | 9 | const fn = (arg1, arg2, ...otherArgs) => { 10 | test.strictSame(arg1, 'someVal'); 11 | test.strictSame(arg2, 4); 12 | test.strictSame(otherArgs, []); 13 | callCount++; 14 | if (callCount === 2) { 15 | test.end(); 16 | } 17 | }; 18 | 19 | const throttledFn = metasync.throttle(1, fn, 'someVal', 4); 20 | 21 | throttledFn(); 22 | test.strictSame(callCount, 1); 23 | throttledFn(); 24 | throttledFn(); 25 | test.strictSame(callCount, 1); 26 | }); 27 | 28 | metatests.test('throttle merge args', (test) => { 29 | let callCount = 0; 30 | 31 | const fn = (arg1, arg2, ...otherArgs) => { 32 | test.strictSame(arg1, 'someVal'); 33 | test.strictSame(arg2, 4); 34 | test.strictSame(otherArgs, ['str']); 35 | callCount++; 36 | if (callCount === 2) { 37 | test.end(); 38 | } 39 | }; 40 | 41 | const throttledFn = metasync.throttle(1, fn, 'someVal', 4); 42 | 43 | throttledFn('str'); 44 | test.strictSame(callCount, 1); 45 | throttledFn('str'); 46 | throttledFn('str'); 47 | test.strictSame(callCount, 1); 48 | }); 49 | 50 | metatests.test('throttle without arguments for function', (test) => { 51 | let callCount = 0; 52 | 53 | const fn = (...args) => { 54 | test.strictSame(args, []); 55 | callCount++; 56 | if (callCount === 2) { 57 | test.end(); 58 | } 59 | }; 60 | 61 | const throttledFn = metasync.throttle(1, fn); 62 | 63 | throttledFn(); 64 | test.strictSame(callCount, 1); 65 | throttledFn(); 66 | throttledFn(); 67 | test.strictSame(callCount, 1); 68 | }); 69 | 70 | metatests.test('debounce', (test) => { 71 | let count = 0; 72 | 73 | const fn = (arg1, arg2, ...otherArgs) => { 74 | test.strictSame(arg1, 'someVal'); 75 | test.strictSame(arg2, 4); 76 | test.strictSame(otherArgs, []); 77 | count++; 78 | test.end(); 79 | }; 80 | 81 | const debouncedFn = metasync.debounce(1, fn, 'someVal', 4); 82 | 83 | debouncedFn(); 84 | debouncedFn(); 85 | test.strictSame(count, 0); 86 | }); 87 | 88 | metatests.test('debounce without arguments for function', (test) => { 89 | let count = 0; 90 | 91 | const fn = (...args) => { 92 | test.strictSame(args, []); 93 | count++; 94 | test.end(); 95 | }; 96 | 97 | const debouncedFn = metasync.debounce(1, fn); 98 | 99 | debouncedFn(); 100 | debouncedFn(); 101 | test.strictSame(count, 0); 102 | }); 103 | 104 | metatests.test('timeout with sync function', (test) => { 105 | const syncFn = (callback) => callback(null, 'someVal'); 106 | metasync.timeout(1, syncFn, (err, res, ...args) => { 107 | test.error(err); 108 | test.strictSame(res, 'someVal'); 109 | test.strictSame(args, []); 110 | test.end(); 111 | }); 112 | }); 113 | 114 | metatests.test('timeout', (test) => { 115 | metasync.timeout( 116 | 10, 117 | (callback) => { 118 | setTimeout(() => { 119 | callback(null, 'someVal'); 120 | }, 0); 121 | }, 122 | (err, res, ...args) => { 123 | test.error(err); 124 | test.strictSame(res, 'someVal'); 125 | test.strictSame(args, []); 126 | test.end(); 127 | }, 128 | ); 129 | }); 130 | -------------------------------------------------------------------------------- /tests/async-iterator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const common = require('@metarhia/common'); 6 | 7 | const nodeVerion = common.between(process.version, 'v', '.'); 8 | const testFilePath = path.join(__dirname, './fixtures/iterator.js'); 9 | 10 | if (nodeVerion >= 10) require(testFilePath); 11 | -------------------------------------------------------------------------------- /tests/fixtures/iterator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const { fork } = require('child_process'); 5 | const { once } = require('events'); 6 | 7 | const metatests = require('metatests'); 8 | const { AsyncIterator, asyncIter } = require('../../'); 9 | 10 | const array = [1, 2, 3, 4]; 11 | 12 | metatests.test('new AsyncIterator() on non Iterable', (test) => { 13 | test.throws(() => { 14 | new AsyncIterator(2); 15 | }, new TypeError('Base is not Iterable')); 16 | test.end(); 17 | }); 18 | 19 | metatests.test('AsyncIterator.throttle with min value', (test) => { 20 | const expectedMin = 1; 21 | const { min } = asyncIter([]).throttle(1, expectedMin); 22 | test.strictSame(min, expectedMin); 23 | test.end(); 24 | }); 25 | 26 | metatests.test('new AsyncIterator() on AsyncIterable', (test) => { 27 | const iterator = array[Symbol.iterator](); 28 | const iterable = { 29 | [Symbol.asyncIterator]: () => ({ next: async () => iterator.next() }), 30 | }; 31 | 32 | const iter = asyncIter(iterable); 33 | test.assert(iter instanceof AsyncIterator); 34 | test.end(); 35 | }); 36 | 37 | metatests.test('next returns Promise', (test) => { 38 | const iter = asyncIter(array); 39 | const item = iter.next(); 40 | 41 | test.assert(item instanceof Promise); 42 | test.end(); 43 | }); 44 | 45 | metatests.test('iter returns an AsyncIterator', (test) => { 46 | const iterator = asyncIter(array); 47 | test.assert(iterator instanceof AsyncIterator); 48 | test.end(); 49 | }); 50 | 51 | metatests.test('AsyncIterator is Iterable', async (test) => { 52 | const iterator = asyncIter(array); 53 | let sum = 0; 54 | for await (const value of iterator) { 55 | sum += value; 56 | } 57 | 58 | test.strictSame(sum, 10); 59 | test.end(); 60 | }); 61 | 62 | metatests.test('AsyncIterator.count', async (test) => { 63 | test.strictSame(await asyncIter(array).count(), array.length); 64 | test.end(); 65 | }); 66 | 67 | metatests.test('AsyncIterator.count on consumed iterator', async (test) => { 68 | const count = await asyncIter(array).skip(array.length).count(); 69 | test.strictSame(count, 0); 70 | test.end(); 71 | }); 72 | 73 | metatests.test('AsyncIterator.each', async (test) => { 74 | const iterator = asyncIter(array); 75 | let sum = 0; 76 | await iterator.each((value) => { 77 | sum += value; 78 | }); 79 | 80 | test.strictSame(sum, 10); 81 | test.end(); 82 | }); 83 | 84 | metatests.test('AsyncIterator.forEach', async (test) => { 85 | const iterator = asyncIter(array); 86 | let sum = 0; 87 | await iterator.forEach((value) => { 88 | sum += value; 89 | }); 90 | 91 | test.strictSame(sum, 10); 92 | test.end(); 93 | }); 94 | 95 | metatests.test('AsyncIterator.parallel', async (test) => { 96 | const iterator = asyncIter(array); 97 | let sum = 0; 98 | await iterator.parallel((value) => { 99 | sum += value; 100 | }); 101 | 102 | test.strictSame(sum, 10); 103 | test.end(); 104 | }); 105 | 106 | metatests.test('AsyncIterator.forEach with thisArg ', async (test) => { 107 | const iterator = asyncIter(array); 108 | const obj = { 109 | sum: 0, 110 | fn(value) { 111 | this.sum += value; 112 | }, 113 | }; 114 | 115 | await iterator.forEach(obj.fn, obj); 116 | 117 | test.strictSame(obj.sum, 10); 118 | test.end(); 119 | }); 120 | 121 | metatests.test('AsyncIterator.reduce', async (test) => { 122 | test.strictSame( 123 | await asyncIter(array).reduce((acc, current) => acc + current, 0), 124 | 10, 125 | ); 126 | test.end(); 127 | }); 128 | 129 | metatests.test('AsyncIterator.reduce with no initialValue', async (test) => { 130 | test.strictSame( 131 | await asyncIter(array).reduce((acc, current) => acc + current), 132 | 10, 133 | ); 134 | test.end(); 135 | }); 136 | 137 | metatests.test( 138 | 'AsyncIterator.reduce with no initialValue on consumed iterator', 139 | async (test) => { 140 | const iterator = asyncIter(array); 141 | await test.rejects( 142 | iterator 143 | .reduce(() => {}) 144 | .then(() => iterator.reduce((acc, current) => acc + current)), 145 | new TypeError('Reduce of consumed async iterator with no initial value'), 146 | ); 147 | }, 148 | ); 149 | 150 | metatests.test('AsyncIterator.map', async (test) => { 151 | test.strictSame( 152 | await asyncIter(array) 153 | .map((value) => value * 2) 154 | .toArray(), 155 | [2, 4, 6, 8], 156 | ); 157 | test.end(); 158 | }); 159 | 160 | metatests.test('AsyncIterator.map with thisArg', async (test) => { 161 | const obj = { 162 | multiplier: 2, 163 | mapper(value) { 164 | return value * this.multiplier; 165 | }, 166 | }; 167 | 168 | test.strictSame( 169 | await asyncIter(array).map(obj.mapper, obj).toArray(), 170 | [2, 4, 6, 8], 171 | ); 172 | test.end(); 173 | }); 174 | 175 | metatests.test('AsyncIterator.filter', async (test) => { 176 | test.strictSame( 177 | await asyncIter(array) 178 | .filter((value) => !(value % 2)) 179 | .toArray(), 180 | [2, 4], 181 | ); 182 | test.end(); 183 | }); 184 | 185 | metatests.test('AsyncIterator.filter with thisArg', async (test) => { 186 | const obj = { 187 | divider: 2, 188 | predicate(value) { 189 | return !(value % this.divider); 190 | }, 191 | }; 192 | 193 | test.strictSame( 194 | await asyncIter(array).filter(obj.predicate, obj).toArray(), 195 | [2, 4], 196 | ); 197 | test.end(); 198 | }); 199 | 200 | metatests.test('AsyncIterator.flat', async (test) => { 201 | const array = [[[[1], 2], 3], 4]; 202 | const flatArray = [1, 2, 3, 4]; 203 | const newArray = await asyncIter(array).flat(3).toArray(); 204 | test.strictSame(newArray, flatArray); 205 | test.end(); 206 | }); 207 | 208 | metatests.test('AsyncIterator.flat with no depth', async (test) => { 209 | const array = [[[[1], 2], 3], 4]; 210 | const flatArray = [[[1], 2], 3, 4]; 211 | const newArray = await asyncIter(array).flat().toArray(); 212 | test.strictSame(newArray, flatArray); 213 | test.end(); 214 | }); 215 | 216 | metatests.test('AsyncIterator.flatMap', async (test) => { 217 | const array = [1, 2, 3]; 218 | const result = [1, 1, 2, 2, 3, 3]; 219 | const newArray = await asyncIter(array) 220 | .flatMap((item) => [item, item]) 221 | .toArray(); 222 | 223 | test.strictSame(newArray, result); 224 | test.end(); 225 | }); 226 | 227 | metatests.test( 228 | 'AsyncIterator.flatMap that returns neither AsyncIterator nor Iterable', 229 | async (test) => { 230 | const array = [1, 2, 3]; 231 | const result = [2, 4, 6]; 232 | const newArray = await asyncIter(array) 233 | .flatMap((item) => item * 2) 234 | .toArray(); 235 | 236 | test.strictSame(newArray, result); 237 | test.end(); 238 | }, 239 | ); 240 | 241 | metatests.test('AsyncIterator.flatMap with thisArg', async (test) => { 242 | const obj = { 243 | value: 1, 244 | mapper(item) { 245 | return [item, this.value]; 246 | }, 247 | }; 248 | 249 | const array = [1, 2, 3]; 250 | const result = [1, 1, 2, 1, 3, 1]; 251 | test.strictSame( 252 | await asyncIter(array).flatMap(obj.mapper, obj).toArray(), 253 | result, 254 | ); 255 | test.end(); 256 | }); 257 | 258 | metatests.test('AsyncIterator.zip', async (test) => { 259 | const it = asyncIter(array); 260 | const itr = asyncIter(array).take(3); 261 | const iterator = asyncIter(array).take(2); 262 | 263 | const zipIter = it.zip(itr, iterator); 264 | test.strictSame(await zipIter.toArray(), [ 265 | [1, 1, 1], 266 | [2, 2, 2], 267 | ]); 268 | test.end(); 269 | }); 270 | 271 | metatests.test('AsyncIterator.chain', async (test) => { 272 | const it = asyncIter(array).take(1); 273 | const itr = await asyncIter(array).skip(1).take(1); 274 | const iterator = await asyncIter(array).skip(2).take(2); 275 | test.strictSame(await it.chain(itr, iterator).toArray(), [1, 2, 3, 4]); 276 | test.end(); 277 | }); 278 | 279 | metatests.test('AsyncIterator.take', async (test) => { 280 | const it = asyncIter(array).take(2); 281 | test.strictSame((await it.next()).value, 1); 282 | test.strictSame((await it.next()).value, 2); 283 | test.assert((await it.next()).done); 284 | test.end(); 285 | }); 286 | 287 | metatests.testSync('AsyncIterator.takeWhile', async (test) => { 288 | const it = asyncIter(array).takeWhile((x) => x < 3); 289 | test.strictSame(await it.toArray(), [1, 2]); 290 | test.assert(it.next().done); 291 | }); 292 | 293 | metatests.test('AsyncIterator.skip', async (test) => { 294 | const it = asyncIter(array).skip(2); 295 | test.strictSame((await it.next()).value, 3); 296 | test.strictSame((await it.next()).value, 4); 297 | test.assert((await it.next()).done); 298 | test.end(); 299 | }); 300 | 301 | metatests.test('AsyncIterator.every that must return true', async (test) => { 302 | test.assert(await asyncIter(array).every((item) => item > 0)); 303 | test.end(); 304 | }); 305 | 306 | metatests.test('AsyncIterator.every that must return false', async (test) => { 307 | test.assertNot(await asyncIter(array).every((item) => item % 2)); 308 | test.end(); 309 | }); 310 | 311 | metatests.test('AsyncIterator.every with thisArg', async (test) => { 312 | const obj = { 313 | min: 0, 314 | predicate(value) { 315 | return value > this.min; 316 | }, 317 | }; 318 | 319 | test.assert(await asyncIter(array).every(obj.predicate, obj)); 320 | test.end(); 321 | }); 322 | 323 | metatests.test('AsyncIterator.some that must return true', async (test) => { 324 | test.assert(await asyncIter(array).some((item) => item % 2)); 325 | test.end(); 326 | }); 327 | 328 | metatests.test('AsyncIterator.some that must return false', async (test) => { 329 | test.assertNot(await asyncIter(array).some((item) => item < 0)); 330 | test.end(); 331 | }); 332 | 333 | metatests.test('AsyncIterator.some with thisArg', async (test) => { 334 | const obj = { 335 | max: 2, 336 | predicate(value) { 337 | return value < this.max; 338 | }, 339 | }; 340 | 341 | test.assert(await asyncIter(array).some(obj.predicate, obj)); 342 | test.end(); 343 | }); 344 | 345 | metatests.testSync( 346 | 'AsyncIterator.someCount that must return true', 347 | async (test) => { 348 | test.assert(await asyncIter(array).someCount((item) => item % 2, 2)); 349 | }, 350 | ); 351 | 352 | metatests.testSync( 353 | 'AsyncIterator.someCount that must return false', 354 | async (test) => { 355 | test.assertNot(await asyncIter(array).someCount((item) => item % 2, 3)); 356 | test.assertNot(await asyncIter(array).someCount((item) => item < 0, 1)); 357 | }, 358 | ); 359 | 360 | metatests.testSync('AsyncIterator.someCount with thisArg', async (test) => { 361 | const obj = { 362 | max: 3, 363 | predicate(value) { 364 | return value < this.max; 365 | }, 366 | }; 367 | 368 | test.assert(await asyncIter(array).someCount(obj.predicate, 2, obj)); 369 | }); 370 | 371 | metatests.test('AsyncIterator.find that must find an element', async (test) => { 372 | test.strictSame(await asyncIter(array).find((item) => item % 2 === 0), 2); 373 | test.end(); 374 | }); 375 | 376 | metatests.test( 377 | 'AsyncIterator.find that must not find an element', 378 | async (test) => { 379 | test.strictSame(await asyncIter(array).find((item) => item > 4), undefined); 380 | test.end(); 381 | }, 382 | ); 383 | 384 | metatests.test('AsyncIterator.find with thisArg', async (test) => { 385 | const obj = { 386 | divider: 2, 387 | predicate(value) { 388 | return value % this.divider === 0; 389 | }, 390 | }; 391 | 392 | test.strictSame(await asyncIter(array).find(obj.predicate, obj), 2); 393 | test.end(); 394 | }); 395 | 396 | metatests.test('AsyncIterator.includes that must return true', async (test) => { 397 | test.assert(await asyncIter(array).includes(1)); 398 | test.end(); 399 | }); 400 | 401 | metatests.test('AsyncIterator.includes with a NaN', async (test) => { 402 | test.assert(await asyncIter([1, 2, NaN]).includes(NaN)); 403 | test.end(); 404 | }); 405 | 406 | metatests.test('AsyncIterator.includes must return false', async (test) => { 407 | test.assertNot(await asyncIter(array).includes(0)); 408 | test.end(); 409 | }); 410 | 411 | metatests.test( 412 | 'AsyncIterator.collectTo must collect to given Collection', 413 | async (test) => { 414 | const set = await asyncIter(array).collectTo(Set); 415 | test.strictSame([...set.values()], array); 416 | test.end(); 417 | }, 418 | ); 419 | 420 | metatests.test('AsyncIterator.toArray must convert to array', async (test) => { 421 | test.strictSame(await asyncIter(array).toArray(), array); 422 | test.end(); 423 | }); 424 | 425 | metatests.test( 426 | 'AsyncIterator.collectWith must collect to a provided object', 427 | async (test) => { 428 | const set = new Set(); 429 | await asyncIter(array).collectWith(set, (obj, item) => obj.add(item)); 430 | test.strictSame([...set.values()], array); 431 | test.end(); 432 | }, 433 | ); 434 | 435 | metatests.test('AsyncIterator.throttle', async (test) => { 436 | const pathThrottleFile = path.join(__dirname, './throttle.js'); 437 | const child = fork(pathThrottleFile); 438 | 439 | const [{ sum, ARRAY_SIZE }] = await once(child, 'message'); 440 | test.strictSame(sum, ARRAY_SIZE); 441 | }); 442 | 443 | metatests.testSync( 444 | 'AsyncIterator.enumerate must return tuples', 445 | async (test) => { 446 | let i = 0; 447 | await asyncIter(array) 448 | .enumerate() 449 | .forEach((t) => { 450 | test.strictSame(t, [i, array[i]]); 451 | i++; 452 | }); 453 | }, 454 | ); 455 | 456 | metatests.testSync( 457 | 'AsyncIterator.enumerate must start from 0', 458 | async (test) => { 459 | const it = asyncIter(array); 460 | await it.next(); 461 | let i = 0; 462 | await it.enumerate().forEach((t) => { 463 | test.strictSame(t, [i, array[i + 1]]); 464 | i++; 465 | }); 466 | }, 467 | ); 468 | 469 | metatests.testSync('AsyncIterator.join default', async (test) => { 470 | const actual = await asyncIter(array).join(); 471 | test.strictSame(actual, '1,2,3,4'); 472 | }); 473 | 474 | metatests.testSync('AsyncIterator.join', async (test) => { 475 | const actual = await asyncIter(array).join(', '); 476 | test.strictSame(actual, '1, 2, 3, 4'); 477 | }); 478 | 479 | metatests.testSync('AsyncIterator.join with prefix', async (test) => { 480 | const actual = await asyncIter(array).join(', ', 'a = '); 481 | test.strictSame(actual, 'a = 1, 2, 3, 4'); 482 | }); 483 | 484 | metatests.testSync('AsyncIterator.join with suffix', async (test) => { 485 | const actual = await asyncIter(array).join(', ', '', ' => 10'); 486 | test.strictSame(actual, '1, 2, 3, 4 => 10'); 487 | }); 488 | 489 | metatests.testSync( 490 | 'AsyncIterator.join with prefix and suffix', 491 | async (test) => { 492 | const actual = await asyncIter(array).join(', ', '[', ']'); 493 | test.strictSame(actual, '[1, 2, 3, 4]'); 494 | }, 495 | ); 496 | -------------------------------------------------------------------------------- /tests/fixtures/throttle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { asyncIter } = require('../../'); 4 | 5 | const doSmth = (time) => { 6 | const begin = Date.now(); 7 | while (Date.now() - begin < time); 8 | }; 9 | 10 | const ARRAY_SIZE = 1000; 11 | const TIMER_TIME = 10; 12 | const ITEM_TIME = 1; 13 | const EXPECTED_PERCENT = 0.7; 14 | 15 | let sum = 0; 16 | const arr = new Array(ARRAY_SIZE).fill(1); 17 | 18 | const iter = asyncIter(arr) 19 | .map((number) => { 20 | doSmth(ITEM_TIME); 21 | sum += number; 22 | }) 23 | .throttle(EXPECTED_PERCENT); 24 | 25 | const timer = setInterval(() => doSmth(TIMER_TIME), 0); 26 | 27 | (async () => { 28 | const begin = Date.now(); 29 | await iter.toArray(); 30 | const allTime = Date.now() - begin; 31 | 32 | clearInterval(timer); 33 | 34 | const mapTime = ARRAY_SIZE * ITEM_TIME; 35 | const actualPercent = mapTime / allTime; 36 | 37 | const actualDeviation = Math.abs(actualPercent - EXPECTED_PERCENT); 38 | process.send({ actualDeviation, sum, ARRAY_SIZE }); 39 | })(); 40 | -------------------------------------------------------------------------------- /tests/load/benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const benchmark = {}; 4 | module.exports = benchmark; 5 | 6 | const rpad = (s, char, count) => s + char.repeat(count - s.length); 7 | const lpad = (s, char, count) => char.repeat(count - s.length) + s; 8 | 9 | benchmark.do = (count, tests) => { 10 | let fn; 11 | let i = 0; 12 | const testCount = tests.length; 13 | 14 | const nextTest = () => { 15 | i++; 16 | fn = tests.shift(); 17 | const result = []; 18 | const begin = process.hrtime(); 19 | let j = 0; 20 | 21 | const nextRepeat = () => { 22 | fn((err, res) => { 23 | if (err) { 24 | console.error(err); 25 | } 26 | j++; 27 | result.push(res); 28 | if (j < count) { 29 | nextRepeat(); 30 | } else { 31 | const end = process.hrtime(begin); 32 | const diff = end[0] * 1e9 + end[1]; 33 | const time = lpad(diff.toString(), '.', 15); 34 | const name = rpad(fn.name, '.', 25); 35 | console.log(name + time + ' nanoseconds'); 36 | if (i < testCount) nextTest(); 37 | } 38 | }); 39 | }; 40 | 41 | nextRepeat(); 42 | }; 43 | 44 | nextTest(); 45 | }; 46 | -------------------------------------------------------------------------------- /tests/load/collect.class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../../lib/collector.class.js'); 7 | 8 | const CollectClass = (done) => { 9 | const dc = metasync.collect(6); 10 | dc.done(done); 11 | let i = 0; 12 | setImmediate(() => dc.pick('uno', ++i * 2)); 13 | setImmediate(() => dc.pick('due', ++i * 3)); 14 | setImmediate(() => dc.pick('tre', ++i * 5)); 15 | setImmediate(() => dc.pick('4th', 'key' + ++i)); 16 | setImmediate(() => dc.pick('5th', ++i === 5)); 17 | setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); 18 | }; 19 | 20 | benchmark.do(COUNT, [CollectClass]); 21 | -------------------------------------------------------------------------------- /tests/load/collect.functor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../../lib/collector.functor.js'); 7 | 8 | const CollectFunctor = (done) => { 9 | const dc = metasync.collect(6); 10 | dc.done(done); 11 | let i = 0; 12 | setImmediate(() => dc.pick('uno', ++i * 2)); 13 | setImmediate(() => dc.pick('due', ++i * 3)); 14 | setImmediate(() => dc.pick('tre', ++i * 5)); 15 | setImmediate(() => dc.pick('4th', 'key' + ++i)); 16 | setImmediate(() => dc.pick('5th', ++i === 5)); 17 | setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); 18 | }; 19 | 20 | benchmark.do(COUNT, [CollectFunctor]); 21 | -------------------------------------------------------------------------------- /tests/load/collect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../../lib/collector.js'); 7 | 8 | const CollectPrototype = (done) => { 9 | const dc = metasync.collect(6); 10 | dc.done(done); 11 | let i = 0; 12 | setImmediate(() => dc.pick('uno', ++i * 2)); 13 | setImmediate(() => dc.pick('due', ++i * 3)); 14 | setImmediate(() => dc.pick('tre', ++i * 5)); 15 | setImmediate(() => dc.pick('4th', 'key' + ++i)); 16 | setImmediate(() => dc.pick('5th', ++i === 5)); 17 | setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); 18 | }; 19 | 20 | benchmark.do(COUNT, [CollectPrototype]); 21 | -------------------------------------------------------------------------------- /tests/load/collect.prototype.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../../lib/collector.prototype.js'); 7 | 8 | const CollectOldPrototype = (done) => { 9 | const dc = new metasync.DataCollector(6); 10 | dc.on('done', done); 11 | let i = 0; 12 | setImmediate(() => dc.collect('uno', ++i * 2)); 13 | setImmediate(() => dc.collect('due', ++i * 3)); 14 | setImmediate(() => dc.collect('tre', ++i * 5)); 15 | setImmediate(() => dc.collect('4th', 'key' + ++i)); 16 | setImmediate(() => dc.collect('5th', ++i === 5)); 17 | setImmediate(() => dc.collect('6th', 'key' + ++i * 2)); 18 | }; 19 | 20 | benchmark.do(COUNT, [CollectOldPrototype]); 21 | -------------------------------------------------------------------------------- /tests/load/parallel.collect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../..'); 7 | 8 | const Collect = (done) => { 9 | const dc = metasync.collect(6); 10 | dc.done(done); 11 | let i = 0; 12 | setImmediate(() => dc.pick('uno', ++i * 2)); 13 | setImmediate(() => dc.pick('due', ++i * 3)); 14 | setImmediate(() => dc.pick('tre', ++i * 5)); 15 | setImmediate(() => dc.pick('4th', 'key' + ++i)); 16 | setImmediate(() => dc.pick('5th', ++i === 5)); 17 | setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); 18 | }; 19 | 20 | benchmark.do(COUNT, [Collect]); 21 | -------------------------------------------------------------------------------- /tests/load/parallel.compose.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../..'); 7 | 8 | const testCompose = (done) => { 9 | let i = 0; 10 | const p1 = (context, callback) => { 11 | setImmediate(() => callback(null, ++i * 2)); 12 | }; 13 | const p2 = (context, callback) => { 14 | setImmediate(() => callback(null, ++i * 3)); 15 | }; 16 | const p3 = (context, callback) => { 17 | setImmediate(() => callback(null, ++i * 5)); 18 | }; 19 | const p4 = (context, callback) => { 20 | setImmediate(() => callback(null, 'key ' + ++i)); 21 | }; 22 | const p5 = (context, callback) => { 23 | setImmediate(() => callback(null, ++i === 5)); 24 | }; 25 | const p6 = (context, callback) => { 26 | setImmediate(() => callback(null, 'key' + ++i * 2)); 27 | }; 28 | 29 | const f1 = metasync([[p1, p2, p3, p4, p5, p6]]); 30 | f1(done); 31 | }; 32 | 33 | benchmark.do(COUNT, [testCompose]); 34 | -------------------------------------------------------------------------------- /tests/load/parallel.promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 1000000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | 7 | const PromiseAll = (done) => { 8 | let i = 0; 9 | const p1 = new Promise((resolve) => { 10 | setImmediate(() => resolve({ p1: ++i * 2 })); 11 | }); 12 | const p2 = new Promise((resolve) => { 13 | setImmediate(() => resolve({ p2: ++i * 3 })); 14 | }); 15 | const p3 = new Promise((resolve) => { 16 | setImmediate(() => resolve({ p3: ++i * 5 })); 17 | }); 18 | const p4 = new Promise((resolve) => { 19 | setImmediate(() => resolve({ p4: 'key ' + ++i })); 20 | }); 21 | const p5 = new Promise((resolve) => { 22 | setImmediate(() => resolve({ p5: ++i === 5 })); 23 | }); 24 | const p6 = new Promise((resolve) => { 25 | setImmediate(() => resolve({ p6: 'key' + ++i * 2 })); 26 | }); 27 | 28 | Promise.all([p1, p2, p3, p4, p5, p6]).then((res) => done(null, res)); 29 | }; 30 | 31 | benchmark.do(COUNT, [PromiseAll]); 32 | -------------------------------------------------------------------------------- /tests/load/poolify.array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 10000; 4 | const GETS = 300; 5 | 6 | const benchmark = require('./benchmark.js'); 7 | const metasync = require('../../lib/poolify.js'); 8 | 9 | const poolifyArray = (done) => { 10 | const buffer = () => new Uint32Array(128); 11 | const pool = metasync.poolify(buffer, 10, 100, 200); 12 | 13 | for (let i = 0; i < GETS; i++) { 14 | pool((item) => { 15 | setImmediate(() => { 16 | pool([item]); 17 | if (i === GETS - 1) done(); 18 | }); 19 | }); 20 | } 21 | }; 22 | 23 | benchmark.do(COUNT, [poolifyArray]); 24 | -------------------------------------------------------------------------------- /tests/load/poolify.opt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 10000; 4 | const GETS = 300; 5 | 6 | const benchmark = require('./benchmark.js'); 7 | const metasync = require('../../lib/poolify.opt.js'); 8 | 9 | const poolifyNoMixin = (done) => { 10 | const buffer = () => new Uint32Array(128); 11 | const pool = metasync.poolify(buffer, 10, 100, 200); 12 | 13 | for (let i = 0; i < GETS; i++) { 14 | pool((item) => { 15 | setImmediate(() => { 16 | pool([item]); 17 | if (i === GETS - 1) done(); 18 | }); 19 | }); 20 | } 21 | }; 22 | 23 | benchmark.do(COUNT, [poolifyNoMixin]); 24 | -------------------------------------------------------------------------------- /tests/load/poolify.symbol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const COUNT = 10000; 4 | const GETS = 300; 5 | 6 | const benchmark = require('./benchmark.js'); 7 | const metasync = require('../../lib/poolify.symbol.js'); 8 | 9 | const poolifySymbol = (done) => { 10 | const buffer = () => new Uint32Array(128); 11 | const pool = metasync.poolify(buffer, 10, 100, 200); 12 | 13 | for (let i = 0; i < GETS; i++) { 14 | pool((item) => { 15 | setImmediate(() => { 16 | pool(item); 17 | if (i === GETS - 1) done(); 18 | }); 19 | }); 20 | } 21 | }; 22 | 23 | benchmark.do(COUNT, [poolifySymbol]); 24 | -------------------------------------------------------------------------------- /tests/load/run.sh: -------------------------------------------------------------------------------- 1 | echo Parallel execution: concurrency 6 x 1mln 2 | node tests/load/parallel.promise.js 3 | node tests/load/parallel.compose.js 4 | node tests/load/parallel.collect.js 5 | echo 6 | echo Sequential execution: concurrency 6 x 100k 7 | node tests/load/sequential.promise.js 8 | node tests/load/sequential.compose.js 9 | echo 10 | echo Poolify: array vs symbol 300 times 11 | node tests/load/poolify.array.js 12 | node tests/load/poolify.symbol.js 13 | node tests/load/poolify.opt.js 14 | echo 15 | echo Collector: 1mnl 16 | node tests/load/collect.js 17 | node tests/load/collect.class.js 18 | node tests/load/collect.prototype.js 19 | node tests/load/collect.functor.js 20 | -------------------------------------------------------------------------------- /tests/load/sequential.compose.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const count = 100000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | const metasync = require('../..'); 7 | 8 | const composeSequential = (done) => { 9 | let i = 0; 10 | const p1 = (context, callback) => { 11 | setImmediate(() => callback(null, ++i * 2)); 12 | }; 13 | const p2 = (context, callback) => { 14 | setImmediate(() => callback(null, ++i * 3)); 15 | }; 16 | const p3 = (context, callback) => { 17 | setImmediate(() => callback(null, ++i * 5)); 18 | }; 19 | const p4 = (context, callback) => { 20 | setImmediate(() => callback(null, 'key ' + ++i)); 21 | }; 22 | const p5 = (context, callback) => { 23 | setImmediate(() => callback(null, ++i === 5)); 24 | }; 25 | const p6 = (context, callback) => { 26 | setImmediate(() => callback(null, 'key' + ++i * 2)); 27 | }; 28 | 29 | const f1 = metasync([p1, p2, p3, p4, p5, p6]); 30 | f1(done); 31 | }; 32 | 33 | benchmark.do(count, [composeSequential]); 34 | -------------------------------------------------------------------------------- /tests/load/sequential.promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const count = 100000; 4 | 5 | const benchmark = require('./benchmark.js'); 6 | 7 | const PromiseThen = (done) => { 8 | let i = 0; 9 | const p1 = new Promise((resolve) => { 10 | setImmediate(() => resolve({ p1: ++i * 2 })); 11 | }); 12 | const p2 = new Promise((resolve) => { 13 | setImmediate(() => resolve({ p2: ++i * 3 })); 14 | }); 15 | const p3 = new Promise((resolve) => { 16 | setImmediate(() => resolve({ p3: ++i * 5 })); 17 | }); 18 | const p4 = new Promise((resolve) => { 19 | setImmediate(() => resolve({ p4: 'key ' + ++i })); 20 | }); 21 | const p5 = new Promise((resolve) => { 22 | setImmediate(() => resolve({ p5: ++i === 5 })); 23 | }); 24 | const p6 = new Promise((resolve) => { 25 | setImmediate(() => resolve({ p6: 'key' + ++i * 2 })); 26 | }); 27 | Promise.resolve() 28 | .then(p1) 29 | .then(p2) 30 | .then(p3) 31 | .then(p4) 32 | .then(p5) 33 | .then(p6) 34 | .then((res) => done(null, res)) 35 | .catch(console.error); 36 | }; 37 | 38 | benchmark.do(count, [PromiseThen]); 39 | --------------------------------------------------------------------------------