Repository: metarhia/metasync Branch: master Commit: 8101e1b394c0 Files: 93 Total size: 199.9 KB Directory structure: gitextract_qn698s6o/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── question.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── AUTHORS ├── LICENSE ├── README.md ├── eslint.config.js ├── 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.d.ts ├── metasync.js ├── package.json ├── prettier.config.js ├── 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 └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org root = true [*] end_of_line = lf charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true [{*.js,*.mjs,*.ts,*.json,*.yml}] indent_size = 2 indent_style = space ================================================ FILE: .gitattributes ================================================ * -text ================================================ FILE: .github/FUNDING.yml ================================================ patreon: tshemsedinov ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: usage example or test. **Expected behavior** A clear and concise description of what you expected. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Fedora 30 64-bit] - Node.js version [e.g. 14.15.1] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: Please don't open an issue to ask questions --- Issues on GitHub are intended to be related to problems and feature requests so we recommend not using this medium to ask them here grin. Thanks for understanding! If you have a question, please check out our support groups and channels for developers community: Telegram: - Channel for Metarhia community: https://t.me/metarhia - Group for Metarhia technology stack community: https://t.me/metaserverless - Group for NodeUA community: https://t.me/nodeua ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ - [ ] tests and linter show no problems (`npm t`) - [ ] tests are added/updated for bug fixes and new features - [ ] code is properly formatted (`npm run fix`) - [ ] description of changes is added in CHANGELOG.md - [ ] update .d.ts typings ================================================ FILE: .github/workflows/test.yml ================================================ name: Testing CI on: pull_request jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: node: - 18 - 20 - 22 - 24 - 25 os: - ubuntu-latest - windows-latest - macos-latest steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - run: npm ci - run: npm test ================================================ FILE: .gitignore ================================================ node_modules dist *.log .DS_Store coverage .nyc_output ================================================ FILE: .npmignore ================================================ node_modules *.log .DS_Store ================================================ FILE: .prettierignore ================================================ /dist package.json package-lock.json ================================================ FILE: AUTHORS ================================================ Timur Shemsedinov Alexey Orlenko Vlad Dziuba Dmytro Nechai Oleksandr Kovalchuk Vladyslav Dukhin Arthur Myronenko Alexey Kachan ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016-2025 Metarhia contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Asynchronous Programming Library [![ci status](https://github.com/metarhia/metasync/workflows/Testing%20CI/badge.svg)](https://github.com/metarhia/metasync/actions?query=workflow%3A%22Testing+CI%22+branch%3Amaster) [![snyk](https://snyk.io/test/github/metarhia/metasync/badge.svg)](https://snyk.io/test/github/metarhia/metasync) [![npm version](https://badge.fury.io/js/metasync.svg)](https://badge.fury.io/js/metasync) [![npm downloads/month](https://img.shields.io/npm/dm/metasync.svg)](https://www.npmjs.com/package/metasync) [![npm downloads](https://img.shields.io/npm/dt/metasync.svg)](https://www.npmjs.com/package/metasync) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/metarhia/metasync/blob/master/LICENSE) ## Installation ```bash $ npm install metasync ``` ## Asynchronous functions composition `metasync(fns)(data, done)` - `fns` - array of callback-last functions, callback contranct err-first - `data` - input data (optional) - `done` - err-first callback - Returns: composed callback-last / err-first function ![composition](https://cloud.githubusercontent.com/assets/4405297/16968374/1b81f160-4e17-11e6-96fa-9d7e2b422396.png) ```js const composed = metasync([f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]); ``` - Array of functions gives sequential execution: `[f1, f2, f3]` - Double brackets array of functions gives parallel execution: `[[f1, f2, f3]]` _Example:_ ```js const metasync = require('metasync'); const fs = require('fs'); // Data collector (collect keys by count) const dc = metasync.collect(4); dc.pick('user', { name: 'Marcus Aurelius' }); fs.readFile('HISTORY.md', (err, data) => dc.collect('history', err, data)); dc.take('readme', fs.readFile, 'README.md'); setTimeout(() => dc.pick('timer', { date: new Date() }), 1000); // Key collector (collect certain keys by names) const kc = metasync .collect(['user', 'history', 'readme', 'timer']) .timeout(2000) .distinct() .done((err, data) => console.log(data)); kc.pick('user', { name: 'Marcus Aurelius' }); kc.take('history', fs.readFile, 'HISTORY.md'); kc.take('readme', fs.readFile, 'README.md'); setTimeout(() => kc.pick('timer', { date: new Date() }), 1000); ``` ## API ### callbackify(fn) - `fn`: [``][function] promise-returning function _Returns:_ [``][function] Convert Promise-returning to callback-last / error-first contract ### asyncify(fn) - `fn`: [``][function] regular synchronous function _Returns:_ [``][function] with contract: callback-last / error-first Convert sync function to callback-last / error-first contract ### promiseToCallbackLast(promise, callback) - `promise`: [``][promise] - `callback`: [``][function] Convert Promise to callback-last ### promisify(fn) - `fn`: [``][function] callback-last function _Returns:_ [``][function] Promise-returning function Convert async function to Promise-returning function ### promisifySync(fn) - `fn`: [``][function] regular synchronous function _Returns:_ [``][function] Promise-returning function Convert sync function to Promise object ### map(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] to be executed for each value in the array - `current`: `` current element being processed in the array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `value`: `` - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][array] Asynchronous map (iterate parallel) ### filter(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] to be executed for each value in the array - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `accepted`: [``][boolean] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][array] Asynchrous filter (iterate parallel) _Example:_ ```js metasync.filter( ['data', 'to', 'filter'], (item, callback) => callback(item.length > 2), (err, result) => console.dir(result), ); ``` ### reduce(items, fn, done\[, initial\]) - `items`: [``][array] incoming - `fn`: [``][function] to be executed for each value in array - `previous`: `` value previously returned in the last iteration - `current`: `` current element being processed in the array - `callback`: [``][function] callback for returning value back to reduce function - `err`: [``][error]|[``][null] - `data`: `` resulting value - `counter`: [``][number] index of the current element being processed in array - `items`: [``][array] the array reduce was called upon - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][array] - `initial`: `` optional value to be used as first argument in first iteration Asynchronous reduce ### reduceRight(items, fn, done\[, initial\]) - `items`: [``][array] incoming - `fn`: [``][function] to be executed for each value in array - `previous`: `` value previously returned in the last iteration - `current`: `` current element being processed in the array - `callback`: [``][function] callback for returning value back to reduce function - `err`: [``][error]|[``][null] - `data`: `` resulting value - `counter`: [``][number] index of the current element being processed in array - `items`: [``][array] the array reduce was called upon - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][array] - `initial`: `` optional value to be used as first argument in first iteration Asynchronous reduceRight ### each(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `items`: [``][array] Asynchronous each (iterate in parallel) _Example:_ ```js metasync.each( ['a', 'b', 'c'], (item, callback) => { console.dir({ each: item }); callback(); }, (err, data) => console.dir('each done'), ); ``` ### series(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `items`: [``][array] Asynchronous series _Example:_ ```js metasync.series( ['a', 'b', 'c'], (item, callback) => { console.dir({ series: item }); callback(); }, (err, data) => { console.dir('series done'); }, ); ``` ### find(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `accepted`: [``][boolean] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: `` Asynchronous find (iterate in series) _Example:_ ```js metasync.find( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], (item, callback) => callback(null, item % 3 === 0 && item % 5 === 0), (err, result) => { console.dir(result); }, ); ``` ### every(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `accepted`: [``][boolean] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][boolean] Asynchronous every ### some(items, fn, done) - `items`: [``][array] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `accepted`: [``][boolean] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - `result`: [``][boolean] Asynchronous some (iterate in series) ### asyncMap(items, fn\[, options\]\[, done\]) - `items`: [``][array] incoming dataset - `fn`: [``][function] - `item`: `` - `index`: [``][number] - `options`: [``][object] map params, optional - `min`: [``][number] min number of items in one next call - `percent`: [``][number] ratio of map time to all time - `done`: [``][function] call on done, optional - `err`: [``][error]|[``][null] - `result`: [``][array] Non-blocking synchronous map ### asyncIter(base) - `base`: [``][iterable]|[``][asynciterable] an iterable that is wrapped in [``][asynciterator] _Returns:_ [``][asynciterator] Create an AsyncIterator instance ### class AsyncIterator #### AsyncIterator.prototype.constructor(base) #### async AsyncIterator.prototype.next() #### async AsyncIterator.prototype.count() #### async AsyncIterator.prototype.each(fn, thisArg) #### async AsyncIterator.prototype.forEach(fn, thisArg) #### async AsyncIterator.prototype.parallel(fn, thisArg) #### async AsyncIterator.prototype.every(predicate, thisArg) #### async AsyncIterator.prototype.find(predicate, thisArg) #### async AsyncIterator.prototype.includes(element) #### async AsyncIterator.prototype.reduce(reducer, initialValue) #### async AsyncIterator.prototype.some(predicate, thisArg) #### async AsyncIterator.prototype.someCount(predicate, count, thisArg) #### async AsyncIterator.prototype.collectTo(CollectionClass) #### async AsyncIterator.prototype.collectWith(obj, collector) #### async AsyncIterator.prototype.join(sep = ', ', prefix = '', suffix = '') #### async AsyncIterator.prototype.toArray() #### AsyncIterator.prototype.map(mapper, thisArg) #### AsyncIterator.prototype.filter(predicate, thisArg) #### AsyncIterator.prototype.flat(depth = 1) #### AsyncIterator.prototype.flatMap(mapper, thisArg) #### AsyncIterator.prototype.zip(...iterators) #### AsyncIterator.prototype.chain(...iterators) #### AsyncIterator.prototype.take(amount) #### AsyncIterator.prototype.takeWhile(predicate, thisArg) #### AsyncIterator.prototype.skip(amount) #### AsyncIterator.prototype.throttle(percent, min) #### AsyncIterator.prototype.enumerate() ### collect(expected) - `expected`: [``][number]|[``][string] _Returns:_ [``][collector] Create Collector instance ### class Collector Data collector #### Collector.prototype.constructor(expected) - `expected`: [``][number]|[``][string] count or keys Data collector #### Collector.prototype.collect(key, err, value) - `key`: [``][string] - `err`: [``][error] - `value`: `` _Returns:_ [``][this] Pick or fail key #### Collector.prototype.pick(key, value) - `key`: [``][string] - `value`: `` _Returns:_ [``][this] Pick key #### Collector.prototype.fail(key, err) - `key`: [``][string] - `err`: [``][error] _Returns:_ [``][this] Fail key #### Collector.prototype.take(key, fn, args) - `key`: [``][string] - `fn`: [``][function] - `args`: [``][array] rest arguments, to be passed in fn _Returns:_ [``][this] Take method result #### Collector.prototype.timeout(msec) - `msec`: [``][number] _Returns:_ [``][this] Set timeout #### Collector.prototype.done(callback) - `callback`: [``][function] - `err`: [``][error] - `data`: `` _Returns:_ [``][this] Set on done listener #### Collector.prototype.finalize(key, err, data) #### Collector.prototype.distinct(value) - `value`: [``][boolean] _Returns:_ [``][this] Deny or allow unlisted keys #### Collector.prototype.cancel(err) #### Collector.prototype.then(fulfill, reject) ### compose(flow) - `flow`: [``][function] callback-last / err-first _Returns:_ [``][function] composed callback-last / err-first Asynchronous functions composition Array of functions results in sequential execution: `[f1, f2, f3]` Double brackets array of functions results in parallel execution: `[[f1, f2, f3]]` _Example:_ ```js const composed = metasync([f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]); ``` ### class Composition #### Composition.prototype.constructor() #### Composition.prototype.on(name, callback) #### Composition.prototype.finalize(err) #### Composition.prototype.collect(err, result) #### Composition.prototype.parallel() #### Composition.prototype.sequential() #### Composition.prototype.then(fulfill, reject) #### Composition.prototype.clone() Clone composed #### Composition.prototype.pause() Pause execution #### Composition.prototype.resume() Resume execution #### Composition.prototype.timeout(msec) - `msec`: [``][number] Set timeout #### Composition.prototype.cancel() Cancel execution where possible ### firstOf(fns, callback) - `fns`: [``][function] callback-last / err-first - `callback`: [``][function] on done, err-first Executes all asynchronous functions and pass first result to callback ### parallel(fns\[, context\], callback) - `fns`: [``][function] callback-last / err-first - `context`: [``][object] incoming data, optional - `callback`: [``][function] on done, err-first Parallel execution _Example:_ ```js metasync.parallel([f1, f2, f3], (err, data) => {}); ``` ### sequential(fns\[, context\], callback) - `fns`: [``][function] callback-last with err-first contract - `context`: [``][object] incoming data, optional - `callback`: [``][function] err-first on done Sequential execution _Example:_ ```js metasync.sequential([f1, f2, f3], (err, data) => {}); ``` ### runIf(condition\[, defaultVal\], asyncFn, ...args) - `condition`: `` - `defaultVal`: `` optional, value that will be returned to callback if `condition` is falsy. - `asyncFn`: [``][function] callback-last function that will be executed if `condition` if truthy - `args`: `` args to pass to `asyncFn` Run `asyncFn` if `condition` is truthy, else return `defaultVal` to callback. ### runIfFn(asyncFn, ...args) - `asyncFn`: [``][function] callback-last function that will be executed if it is provided - `args`: `` args to pass to `asyncFn` Run `asyncFn` if it is provided ### class do #### do.prototype.constructor(fn, ...args) ### toAsync(fn) - `fn`: [``][function] callback-last / err-first _Returns:_ [``][function] Convert synchronous function to asynchronous Transform function with args arguments and callback to function with args as separate values and callback ### asAsync(fn, args) - `fn`: [``][function] asynchronous - `args`: [``][array] its arguments Wrap function adding async chain methods ### of(args) - `args`: [``][array] Applicative f => a -> f a ### concat(fn1, fn2) - `fn1`: [``][function] - `fn2`: [``][function] Monoid m => a -> a -> a ### fmap(fn1, f) - `fn1`: [``][function] - `f`: [``][function] Functor f => (a -> b) -> f a -> f b ### ap(fn, funcA) - `fn`: [``][function] - `funcA`: [``][function] Applicative f => f (a -> b) -> f a -> f b ### memoize(fn) - `fn`: [``][function] sync or async _Returns:_ [``][function] memoized Create memoized function ### class Memoized #### Memoized.prototype.constructor() #### Memoized.prototype.clear() #### Memoized.prototype.add(key, err, data) #### Memoized.prototype.del(key) #### Memoized.prototype.get(key, callback) #### Memoized.prototype.on(eventName, listener) - `eventName`: [``][string] - `listener`: [``][function] handler Add event listener _Example:_ ```js const memoized = new Memoized(); memoized.on('memoize', (err, data) => { ... }); memoized.on('add', (key, err, data) => { ... }); memoized.on('del', (key) => { ... }) memoized.on('clear', () => { ... }); ``` #### Memoized.prototype.emit(eventName, args) - `eventName`: [``][string] - `args`: `` rest arguments Emit Memoized events ### poolify(factory, min, norm, max) ### queue(concurrency) - `concurrency`: [``][number] simultaneous and asynchronously executing tasks _Returns:_ [``][queue] Create Queue instance ### class Queue Queue constructor #### Queue.prototype.constructor(concurrency) - `concurrency`: [``][number] asynchronous concurrency Queue constructor #### Queue.prototype.wait(msec) - `msec`: [``][number] wait timeout for single item _Returns:_ [``][this] Set wait before processing timeout #### Queue.prototype.throttle(count\[, interval\]) - `count`: [``][number] item count - `interval`: [``][number] per interval, optional default: 1000 msec _Returns:_ [``][this] Throttle to limit throughput #### Queue.prototype.add(item\[, factor\[, priority\]\]) - `item`: [``][object] to be added - `factor`: [``][number]|[``][string] type, source, destination or path, optional - `priority`: [``][number] optional _Returns:_ [``][this] Add item to queue #### Queue.prototype.next(task) - `task`: [``][array] next task [item, factor, priority] _Returns:_ [``][this] Process next item #### Queue.prototype.takeNext() _Returns:_ [``][this] Prepare next item for processing #### Queue.prototype.pause() _Returns:_ [``][this] Pause queue This function is not completely implemented yet #### Queue.prototype.resume() _Returns:_ [``][this] Resume queue This function is not completely implemented yet #### Queue.prototype.clear() _Returns:_ [``][this] Clear queue #### Queue.prototype.timeout(msec, onTimeout) - `msec`: [``][number] process timeout for single item - `onTimeout`: [``][function] _Returns:_ [``][this] Set timeout interval and listener #### Queue.prototype.process(fn) - `fn`: [``][function] - `item`: [``][object] - `callback`: [``][function] - `err`: [``][error]|[``][null] - `result`: `` _Returns:_ [``][this] Set processing function #### Queue.prototype.done(fn) - `fn`: [``][function] done listener - `err`: [``][error]|[``][null] - `result`: `` _Returns:_ [``][this] Set listener on processing done #### Queue.prototype.success(listener) - `listener`: [``][function] on success - `item`: `` _Returns:_ [``][this] Set listener on processing success #### Queue.prototype.failure(listener) - `listener`: [``][function] on failure - `err`: [``][error]|[``][null] _Returns:_ [``][this] Set listener on processing error #### Queue.prototype.drain(listener) - `listener`: [``][function] on drain _Returns:_ [``][this] Set listener on drain Queue #### Queue.prototype.fifo() _Returns:_ [``][this] Switch to FIFO mode (default for Queue) #### Queue.prototype.lifo() _Returns:_ [``][this] Switch to LIFO mode #### Queue.prototype.priority(flag) - `flag`: [``][boolean] default: true, false will disable priority mode _Returns:_ [``][this] Activate or deactivate priority mode #### Queue.prototype.roundRobin(flag) - `flag`: [``][boolean] default: true, false will disable roundRobin mode _Returns:_ [``][this] Activate or deactivate round robin mode #### Queue.prototype.pipe(dest) - `dest`: [``][queue] destination queue _Returns:_ [``][this] Pipe processed items to different queue ### throttle(timeout, fn, ...args) - `timeout`: [``][number] msec interval - `fn`: [``][function] to be throttled - `args`: [``][array] arguments for fn, optional _Returns:_ [``][function] Get throttling function, executed once per interval ### debounce(timeout, fn, ...args) - `timeout`: [``][number] msec - `fn`: [``][function] to be debounced - `args`: [``][array] arguments for fn, optional Debounce function, delayed execution ### timeout(timeout, fn, callback) - `timeout`: [``][number] time interval - `fn`: [``][function] to be executed - `callback`: [``][function] callback(...args), on done - `args`: [``][array] Set timeout for asynchronous function execution ## Contributors - Timur Shemsedinov (marcusaurelius) - See github for full [contributors list](https://github.com/metarhia/metasync/graphs/contributors) [asynciterable]: https://tc39.github.io/ecma262/#sec-asynciterable-interface [asynciterator]: #class-asynciterator [collector]: #class-collector [queue]: #class-queue [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object [function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function [promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise [array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array [error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error [boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type [null]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Null_type [number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type [string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type [iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols [this]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this ================================================ FILE: eslint.config.js ================================================ 'use strict'; const init = require('eslint-config-metarhia'); init[0].ignores.push('./dist/**/*.js'); init[0].rules['no-invalid-this'] = 'off'; module.exports = [...init]; ================================================ FILE: lib/adapters.js ================================================ 'use strict'; // Convert Promise to callback-last // promise // callback const promiseToCallbackLast = (promise) => (callback) => { promise.then( (value) => { callback(null, value); }, (reason) => { callback(reason); }, ); }; // Convert Promise-returning to callback-last / error-first contract // fn promise-returning function // // Returns: const callbackify = (fn) => (...args) => { const callback = args.pop(); promiseToCallbackLast(fn(...args))(callback); }; // Convert sync function to callback-last / error-first contract // fn regular synchronous function // // Returns: with contract: callback-last / error-first const asyncify = (fn) => (...args) => { const callback = args.pop(); setTimeout(() => { let result; try { result = fn(...args); } catch (error) { return void callback(error); } callback(null, result); }, 0); }; // Convert async function to Promise-returning function // fn callback-last function // // Returns: Promise-returning function const promisify = (fn) => (...args) => new Promise((resolve, reject) => { fn(...args, (err, data) => { if (err) reject(err); else resolve(data); }); }); // Convert sync function to Promise object // fn regular synchronous function // // Returns: Promise-returning function const promisifySync = (fn) => (...args) => { let result; try { result = fn(...args); } catch (error) { return Promise.reject(error); } return Promise.resolve(result); }; module.exports = { callbackify, asyncify, promiseToCallbackLast, promisify, promisifySync, }; ================================================ FILE: lib/array.js ================================================ 'use strict'; // Asynchronous map (iterate parallel) // items - , incoming // fn - , to be executed for each value in the array // current - , current element being processed in the array // callback - // err - | // value - // done - , on done // err - | // result - const map = (items, fn, done) => { const len = items.length; if (!len) return void done(null, []); let errored = false; let count = 0; const result = new Array(len); const next = (index, err, value) => { if (errored) return; if (err) { errored = true; return void done(err); } result[index] = value; count++; if (count === len) done(null, result); }; for (let i = 0; i < len; i++) { fn(items[i], next.bind(null, i)); } }; const DEFAULT_OPTIONS = { min: 5, percent: 0.7 }; // Non-blocking synchronous map // Signature: items, fn[, options][, done] // items - , incoming dataset // fn - // item - // index - // options - , map params, optional // min - , min number of items in one next call // percent - , ratio of map time to all time // done - , call on done, optional // err - | // result - const asyncMap = (items, fn, options = {}, done) => { if (typeof options === 'function') { done = options; options = DEFAULT_OPTIONS; } if (!items.length) { if (done) done(null, []); return; } const min = options.min || DEFAULT_OPTIONS.min; const percent = options.percent || DEFAULT_OPTIONS.percent; let begin; let sum = 0; let count = 0; const result = done ? new Array(items.length) : null; const ratio = percent / (1 - percent); const countNumber = () => { const loopTime = Date.now() - begin; const itemTime = sum / count; const necessaryNumber = (ratio * loopTime) / itemTime; return Math.max(necessaryNumber, min); }; const next = () => { const itemsNumber = count ? countNumber() : min; const iterMax = Math.min(items.length, itemsNumber + count); begin = Date.now(); for (; count < iterMax; count++) { const itemResult = fn(items[count], count); if (done) result[count] = itemResult; } sum += Date.now() - begin; if (count < items.length) { begin = Date.now(); setTimeout(next, 0); } else if (done) { done(null, result); } }; next(); }; // Asynchrous filter (iterate parallel) // items - , incoming // fn - , to be executed for each value in the array // value - , item from items array // callback - // err - | // accepted - // done - , on done // err - | // result - // // Example: // metasync.filter( // ['data', 'to', 'filter'], // (item, callback) => callback(item.length > 2), // (err, result) => console.dir(result) // ); const filter = (items, fn, done) => { const len = items.length; if (!len) return void done(null, []); let count = 0; let suitable = 0; const data = new Array(len); const rejected = Symbol('rejected'); const next = (index, err, accepted) => { if (!accepted || err) { data[index] = rejected; } else { data[index] = items[index]; suitable++; } count++; if (count === len) { const result = new Array(suitable); let pos = 0; for (let i = 0; i < len; i++) { const val = data[i]; if (val !== rejected) result[pos++] = val; } done(null, result); } }; for (let i = 0; i < len; i++) { fn(items[i], next.bind(null, i)); } }; const REDUCE_EMPTY_ARR = 'Metasync: reduce of empty array with no initial value'; // Asynchronous reduce // Signature: items, fn, done[, initial] // items - , incoming // fn - , to be executed for each value in array // previous - , value previously returned in the last iteration // current - , current element being processed in the array // callback - , callback for returning value // back to reduce function // err - | // data - , resulting value // counter - , index of the current element // being processed in array // items - , the array reduce was called upon // done - , on done // err - | // result - // initial - , optional value to be used as first // argument in first iteration const reduce = (items, fn, done, initial) => { const len = items.length; const hasInitial = typeof initial !== 'undefined'; if (len === 0 && !hasInitial) { return void done(new TypeError(REDUCE_EMPTY_ARR), initial); } let previous = hasInitial ? initial : items[0]; if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) { return void done(null, previous); } let count = hasInitial ? 0 : 1; let current = items[count]; const last = len - 1; const next = (err, data) => { if (err) return void done(err); if (count === last) return void done(null, data); count++; previous = data; current = items[count]; fn(previous, current, next, count, items); }; fn(previous, current, next, count, items); }; const REDUCE_RIGHT_EMPTY_ARR = 'Metasync: reduceRight of empty array with no initial value'; // Asynchronous reduceRight // Signature: items, fn, done[, initial] // items - , incoming // fn - , to be executed for each value in array // previous - , value previously returned in the last iteration // current - , current element being processed in the array // callback - , callback for returning value // back to reduce function // err - | // data - , resulting value // counter - , index of the current element // being processed in array // items - , the array reduce was called upon // done - , on done // err - | // result - // initial - , optional value to be used as first // argument in first iteration const reduceRight = (items, fn, done, initial) => { const len = items.length; const hasInitial = typeof initial !== 'undefined'; if (len === 0 && !hasInitial) { return void done(new TypeError(REDUCE_RIGHT_EMPTY_ARR), initial); } let previous = hasInitial ? initial : items[len - 1]; if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) { return void done(null, previous); } let count = hasInitial ? len - 1 : len - 2; let current = items[count]; const last = 0; const next = (err, data) => { if (err) return void done(err); if (count === last) return void done(null, data); count--; previous = data; current = items[count]; fn(previous, current, next, count, items); }; fn(previous, current, next, count, items); }; // Asynchronous each (iterate in parallel) // items - , incoming // fn - // value - , item from items array // callback - // err - | // done - , on done // err - | // items - // // Example: // metasync.each( // ['a', 'b', 'c'], // (item, callback) => { // console.dir({ each: item }); // callback(); // }, // (err, data) => console.dir('each done') // ); const each = (items, fn, done) => { const len = items.length; if (len === 0) return void done(null, items); let count = 0; let errored = false; const next = (err) => { if (errored) return; if (err) { errored = true; return void done(err); } count++; if (count === len) done(null); }; for (let i = 0; i < len; i++) { fn(items[i], next); } }; // Asynchronous series // items - , incoming // fn - // value - , item from items array // callback - // err - | // done - , on done // err - | // items - // // Example: // metasync.series( // ['a', 'b', 'c'], // (item, callback) => { // console.dir({ series: item }); // callback(); // }, // (err, data) => { // console.dir('series done'); // } // ); const series = (items, fn, done) => { const len = items.length; let i = -1; const next = () => { i++; if (i === len) return void done(null, items); fn(items[i], (err) => { if (err) return void done(err); setImmediate(next); }); }; next(); }; // Asynchronous find (iterate in series) // items - , incoming // fn - , // value - , item from items array // callback - // err - | // accepted - // done - , on done // err - | // result - // // Example: // metasync.find( // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], // (item, callback) => callback(null, item % 3 === 0 && item % 5 === 0), // (err, result) => { // console.dir(result); // } // ); const find = (items, fn, done) => { const len = items.length; if (len === 0) return void done(); let finished = false; const last = len - 1; const next = (index, err, accepted) => { if (finished) return; if (err) { finished = true; return void done(err); } if (accepted) { finished = true; return void done(null, items[index]); } if (index === last) done(null); }; for (let i = 0; i < len; i++) { fn(items[i], next.bind(null, i)); } }; // Asynchronous every // items - , incoming // fn - , // value - , item from items array // callback - // err - | // accepted - // done - , on done // err - | // result - const every = (items, fn, done) => { if (items.length === 0) return void done(null, true); let proceedItemsCount = 0; const len = items.length; const finish = (err, accepted) => { if (!done) return; if (err || !accepted) { done(err, false); done = null; return; } proceedItemsCount++; if (proceedItemsCount === len) done(null, true); }; for (const item of items) fn(item, finish); }; // Asynchronous some (iterate in series) // items - , incoming // fn - // value - , item from items array // callback - // err - | // accepted - // done - , on done // err - | // result - const some = (items, fn, done) => { const len = items.length; let i = 0; const next = () => { if (i === len) return void done(null, false); fn(items[i], (err, accepted) => { if (err) return void done(err); if (accepted) return void done(null, true); i++; next(); }); }; if (len > 0) next(); else done(null, false); }; module.exports = { map, filter, reduce, reduceRight, each, series, find, every, some, asyncMap, }; ================================================ FILE: lib/async-iterator.js ================================================ /* eslint-disable no-use-before-define */ 'use strict'; const { promisify } = require('util'); const timeout = promisify((res) => setTimeout(res, 0)); const toIterator = (base) => { if (base[Symbol.asyncIterator]) { return base[Symbol.asyncIterator](); } else if (base[Symbol.iterator]) { return base[Symbol.iterator](); } else { throw new TypeError('Base is not Iterable'); } }; class AsyncIterator { constructor(base) { this.base = toIterator(base); } [Symbol.asyncIterator]() { return this; } async next() { return this.base.next(); } async count() { let count = 0; while (!(await this.next()).done) { count++; } return count; } async each(fn, thisArg) { return this.forEach(fn, thisArg); } async forEach(fn, thisArg) { for await (const value of this) { await fn.call(thisArg, value); } } async parallel(fn, thisArg) { const promises = []; for await (const value of this) { promises.push(fn.call(thisArg, value)); } return Promise.all(promises); } async every(predicate, thisArg) { for await (const value of this) { const res = await predicate.call(thisArg, value); if (!res) return false; } return true; } async find(predicate, thisArg) { for await (const value of this) { if (await predicate.call(thisArg, value)) { return value; } } const value = undefined; return value; } async includes(element) { for await (const value of this) { if (value === element || (Number.isNaN(value) && Number.isNaN(element))) { return true; } } return false; } async reduce(reducer, initialValue) { let result = initialValue; if (result === undefined) { const next = await this.next(); if (next.done) { throw new TypeError( 'Reduce of consumed async iterator with no initial value', ); } result = next.value; } for await (const value of this) { result = await reducer(result, value); } return result; } async some(predicate, thisArg) { for await (const value of this) { if (await predicate.call(thisArg, value)) { return true; } } return false; } async someCount(predicate, count, thisArg) { let n = 0; for await (const value of this) { if (await predicate.call(thisArg, value)) { if (++n === count) return true; } } return false; } async collectTo(CollectionClass) { const arr = await this.toArray(); return new CollectionClass(arr); } async collectWith(obj, collector) { await this.forEach((element) => collector(obj, element)); } async join(sep = ',', prefix = '', suffix = '') { let result = prefix; const { done, value } = await this.next(); if (!done) { result += value; for await (const value of this) { result += sep + value; } } return result + suffix; } async toArray() { const newArray = []; for await (const value of this) { newArray.push(value); } return newArray; } map(mapper, thisArg) { return new MapIterator(this, mapper, thisArg); } filter(predicate, thisArg) { return new FilterIterator(this, predicate, thisArg); } flat(depth = 1) { return new FlatIterator(this, depth); } flatMap(mapper, thisArg) { return new FlatMapIterator(this, mapper, thisArg); } zip(...iterators) { return new ZipIterator(this, iterators); } chain(...iterators) { return new ChainIterator(this, iterators); } take(amount) { return new TakeIterator(this, amount); } takeWhile(predicate, thisArg) { return new TakeWhileIterator(this, predicate, thisArg); } skip(amount) { for (let i = 0; i < amount; i++) { this.next(); } return this; } throttle(percent, min) { return new ThrottleIterator(this, percent, min); } enumerate() { return new EnumerateIterator(this); } } class MapIterator extends AsyncIterator { constructor(base, mapper, thisArg) { super(base); this.mapper = mapper; this.thisArg = thisArg; } async next() { const { done, value } = await this.base.next(); return { done, value: done ? undefined : await this.mapper.call(this.thisArg, value), }; } } class FilterIterator extends AsyncIterator { constructor(base, predicate, thisArg) { super(base); this.predicate = predicate; this.thisArg = thisArg; } async next() { for await (const value of this.base) { if (await this.predicate.call(this.thisArg, value)) { return { done: false, value }; } } return { done: true, value: undefined }; } } class FlatIterator extends AsyncIterator { constructor(base, depth) { super(base); this.currentDepth = 0; this.stack = new Array(depth + 1); this.stack[0] = base; } async next() { while (this.currentDepth >= 0) { const top = this.stack[this.currentDepth]; const next = await top.next(); if (next.done) { this.stack[this.currentDepth] = null; this.currentDepth--; continue; } if ( this.currentDepth === this.stack.length - 1 || (!next.value[Symbol.iterator] && !next.value[Symbol.asyncIterator]) ) { return next; } this.stack[++this.currentDepth] = next.value[Symbol.asyncIterator] ? next.value[Symbol.asyncIterator]() : next.value[Symbol.iterator](); } return { done: true, value: undefined }; } } class FlatMapIterator extends AsyncIterator { constructor(base, mapper, thisArg) { super(base); this.mapper = mapper; this.thisArg = thisArg; this.currentIterator = null; } async next() { if (!this.currentIterator) { const next = await this.base.next(); if (next.done) { return next; } const value = this.mapper.call(this.thisArg, next.value); if (!value[Symbol.iterator] && !value[Symbol.asyncIterator]) { return { done: false, value }; } this.currentIterator = toIterator(value); } const next = await this.currentIterator.next(); if (next.done) { this.currentIterator = null; return this.next(); } return next; } } class TakeIterator extends AsyncIterator { constructor(base, amount) { super(base); this.amount = amount; this.iterated = 0; } async next() { this.iterated++; if (this.iterated <= this.amount) { return this.base.next(); } return { done: true, value: undefined }; } } class TakeWhileIterator extends AsyncIterator { constructor(base, predicate, thisArg) { super(base); this.predicate = predicate; this.thisArg = thisArg; this.done = false; } async next() { if (this.done) return { done: true, value: undefined }; const next = await this.base.next(); const res = await this.predicate.call(this.thisArg, next.value); if (!next.done && res) return next; this.done = true; return { done: true, value: undefined }; } } class ZipIterator extends AsyncIterator { constructor(base, iterators) { super(base); this.iterators = iterators.map(toIterator); } async next() { const result = []; const next = await this.base.next(); if (next.done) { return next; } result.push(next.value); for (const iterator of this.iterators) { const next = await iterator.next(); if (next.done) { return next; } result.push(next.value); } return { done: false, value: result }; } } class ChainIterator extends AsyncIterator { constructor(base, iterators) { super(base); this.currentIterator = base; this.iterators = iterators.map(toIterator)[Symbol.iterator](); } async next() { const next = await this.currentIterator.next(); if (!next.done) { return next; } const iterator = this.iterators.next(); if (iterator.done) { return iterator; } this.currentIterator = iterator.value; return this.next(); } } class EnumerateIterator extends AsyncIterator { constructor(base) { super(base); this.index = 0; } async next() { const next = await this.base.next(); if (next.done) { return next; } return { done: false, value: [this.index++, next.value] }; } } class ThrottleIterator extends AsyncIterator { constructor(base, percent = 0.7, min = 5) { super(base); this.min = min; this.ratio = percent / (1 - percent); this.sum = 0; this.count = 0; this.begin = Date.now(); this.iterMax = this.min; } async next() { if (this.iterMax > this.count) { this.count++; return this.base.next(); } this.sum += Date.now() - this.begin; const itemTime = this.sum / this.count; this.begin = Date.now(); await timeout(); const loopTime = Date.now() - this.begin; const number = Math.max((this.ratio * loopTime) / itemTime, this.min); this.iterMax = Math.round(number) + this.count; this.count++; this.begin = Date.now(); return this.base.next(); } } // Create an AsyncIterator instance // base - | , an iterable // that is wrapped in // // Returns: const asyncIter = (base) => new AsyncIterator(base); module.exports = { asyncIter, AsyncIterator }; ================================================ FILE: lib/collector.class.js ================================================ 'use strict'; const emptiness = () => {}; const once = (callback) => { let called = false; return (...args) => { if (!called) { called = true; callback(...args); } }; }; const UNEXPECTED_KEY = 'Metasync: unexpected key: '; const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; const COLLECT_CANCELED = 'Metasync: Collector cancelled'; class Collector { // Collector constructor // expected - | , count or keys constructor(expected) { this.expectKeys = Array.isArray(expected) ? new Set(expected) : null; this.expected = this.expectKeys ? expected.length : expected; this.keys = new Set(); this.count = 0; this.timer = null; this.onDone = emptiness; this.isDistinct = false; this.isDone = false; this.data = {}; } collect(key, err, value) { if (this.isDone) return this; if (err) { this.finalize(err, this.data); return this; } if (this.expectKeys && !this.expectKeys.has(key)) { if (this.isDistinct) { const err = new Error(UNEXPECTED_KEY + key); this.finalize(err, this.data); return this; } } else if (!this.keys.has(key)) { this.count++; } this.data[key] = value; this.keys.add(key); if (this.expected === this.count) { this.finalize(null, this.data); } return this; } pick(key, value) { this.collect(key, null, value); return this; } fail(key, err) { this.collect(key, err); return this; } take(key, fn, ...args) { fn(...args, (err, data) => { this.collect(key, err, data); }); return this; } timeout(msec) { if (this.timer) { clearTimeout(this.timer); this.timer = null; } if (msec > 0) { this.timer = setTimeout(() => { const err = new Error(COLLECT_TIMEOUT); this.finalize(err, this.data); }, msec); } return this; } done(callback) { this.onDone = callback; return this; } finalize(key, err, data) { if (this.isDone) return this; if (this.onDone) { if (this.timer) { clearTimeout(this.timer); this.timer = null; } this.isDone = true; this.onDone(key, err, data); } return this; } distinct(value = true) { this.isDistinct = value; return this; } cancel(err) { err = err || new Error(COLLECT_CANCELED); this.finalize(err, this.data); return this; } then(fulfilled, rejected) { const fulfill = once(fulfilled); const reject = once(rejected); this.onDone = (err, result) => { if (err) reject(err); else fulfill(result); }; return this; } } // Collector instance constructor // expected - | // // Returns: const collect = (expected) => new Collector(expected); module.exports = { collect }; ================================================ FILE: lib/collector.functor.js ================================================ 'use strict'; const TYPE_ERROR = 'Metasync: Collect unexpected type'; const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; // Collector instance constructor // expected - | , count or keys // // Returns: , functor, collector const collect = (expected) => { const isCount = typeof expected === 'number'; const isKeys = Array.isArray(expected); if (!(isCount || isKeys)) throw new TypeError(TYPE_ERROR); let keys = null; if (isKeys) { keys = new Set(expected); expected = expected.length; } let count = 0; let timer = null; let onDone = null; let isDistinct = false; let isDone = false; const data = {}; const collector = (key, err, value) => { if (isDone) return collector; if (!isDistinct || !(key in data)) { if (!isCount && !keys.has(key)) return collector; count++; } if (err) { collector.finalize(err, data); return collector; } data[key] = value; if (expected === count) { if (timer) clearTimeout(timer); collector.finalize(null, data); } return collector; }; const methods = { pick: (key, value) => collector(key, null, value), fail: (key, err) => collector(key, err), take: (key, fn, ...args) => { fn(...args, (err, data) => collector(key, err, data)); return collector; }, timeout: (msec) => { if (msec) { timer = setTimeout(() => { const err = new Error(COLLECT_TIMEOUT); collector.finalize(err, data); }, msec); timer.unref(); } return collector; }, // Call on done // callback - // error - | // data - done: (callback) => { onDone = callback; return collector; }, finalize: (err, data) => { if (isDone) return collector; isDone = true; if (onDone) onDone(err, data); return collector; }, distinct: (value = true) => { isDistinct = value; return collector; }, }; return Object.assign(collector, methods); }; module.exports = { collect }; ================================================ FILE: lib/collector.js ================================================ 'use strict'; const emptiness = () => {}; const UNEXPECTED_KEY = 'Metasync: unexpected key: '; const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; const COLLECT_CANCELED = 'Metasync: Collector cancelled'; // Data collector // expected - | , count or keys function Collector(expected) { this.expectKeys = Array.isArray(expected) ? new Set(expected) : null; this.expected = this.expectKeys ? expected.length : expected; this.keys = new Set(); this.count = 0; this.timer = null; this.onDone = emptiness; this.isDistinct = false; this.isDone = false; this.data = {}; } // Pick or fail key // key - // err - // value - // // Returns: Collector.prototype.collect = function (key, err, value) { if (this.isDone) return this; if (err) { this.finalize(err, this.data); return this; } if (this.expectKeys && !this.expectKeys.has(key)) { if (this.isDistinct) { const err = new Error(UNEXPECTED_KEY + key); this.finalize(err, this.data); return this; } } else if (!this.keys.has(key)) { this.count++; } this.data[key] = value; this.keys.add(key); if (this.expected === this.count) { this.finalize(null, this.data); } return this; }; // Pick key // key - // value - // // Returns: Collector.prototype.pick = function (key, value) { this.collect(key, null, value); return this; }; // Fail key // key - // err - // // Returns: Collector.prototype.fail = function (key, err) { this.collect(key, err); return this; }; // Take method result // key - // fn - // args - , rest arguments, to be passed in fn // // Returns: Collector.prototype.take = function (key, fn, ...args) { fn(...args, (err, data) => { this.collect(key, err, data); }); return this; }; // Set timeout // msec - // // Returns: Collector.prototype.timeout = function (msec) { if (this.timer) { clearTimeout(this.timer); this.timer = null; } if (msec > 0) { this.timer = setTimeout(() => { const err = new Error(COLLECT_TIMEOUT); this.finalize(err, this.data); }, msec); } return this; }; // Set on done listener // callback - // err - // data - // // Returns: Collector.prototype.done = function (callback) { this.onDone = callback; return this; }; Collector.prototype.finalize = function (key, err, data) { if (this.isDone) return this; if (this.timer) { clearTimeout(this.timer); this.timer = null; } this.isDone = true; this.onDone(key, err, data); return this; }; // Deny or allow unlisted keys // value - // // Returns: Collector.prototype.distinct = function (value = true) { this.isDistinct = value; return this; }; Collector.prototype.cancel = function (err) { err = err || new Error(COLLECT_CANCELED); this.finalize(err, this.data); return this; }; Collector.prototype.then = function (fulfill, reject) { if (!fulfill) fulfill = emptiness; if (!reject) reject = emptiness; this.onDone = (err, result) => { this.onDone = emptiness; if (err) reject(err); else fulfill(result); }; return this; }; // Create Collector instance // expected - | // // Returns: const collect = (expected) => new Collector(expected); module.exports = { collect, Collector }; ================================================ FILE: lib/collector.prototype.js ================================================ 'use strict'; const inherits = (child, parent) => { child.prototype = Object.create(parent.prototype); child.prototype.constructor = child; }; function Collector() {} const COLLECT_TIMEOUT = 'Metasync: Collector timed out'; // Add event listener // eventName - // listener - , handler // // Example: // const collector = new Collector(); // collector.on('error', (err, key) => { ... }); // collector.on('timeout', (err, data) => { ... }); // collector.on('done', (errs, data) => { ... }) Collector.prototype.on = function (eventName, listener) { if (eventName in this.events) { this.events[eventName] = listener; } }; // Emit Collector events // eventName - // err - | Collector.prototype.emit = function (eventName, err, data) { const event = this.events[eventName]; if (event) event(err, data); }; // Create new DataCollector // Signature: expected[, timeout] // expected - , count of `collect()` calls expected // timeout - , collect timeout, optional // // Returns: const DataCollector = function (expected, timeout) { this.expected = expected; this.timeout = timeout; this.count = 0; this.data = {}; this.errs = []; this.events = { error: null, timeout: null, done: null, }; if (this.timeout) { this.timer = setTimeout(() => { const err = new Error(COLLECT_TIMEOUT); this.emit('timeout', err, this.data); }, timeout); } }; inherits(DataCollector, Collector); // Push data to collector // key - , key in result data // data - | , value or error DataCollector.prototype.collect = function (key, data) { this.count++; if (data instanceof Error) { this.errs[key] = data; this.emit('error', data, key); } else { this.data[key] = data; } if (this.expected === this.count) { if (this.timer) clearTimeout(this.timer); const errs = this.errs.length ? this.errs : null; this.emit('done', errs, this.data); } }; // Key Collector // Signature: keys[, timeout] // keys - // timeout - , collect timeout, optional // // Returns: // // Example: new KeyCollector(['config', 'users', 'cities']) const KeyCollector = function (keys, timeout) { this.isDone = false; this.keys = keys; this.expected = keys.length; this.count = 0; this.timeout = timeout; this.data = {}; this.errs = []; this.events = { error: null, timeout: null, done: null, }; const collector = this; if (this.timeout) { this.timer = setTimeout(() => { const err = new Error(COLLECT_TIMEOUT); collector.emit('timeout', err, collector.data); }, timeout); } }; inherits(KeyCollector, Collector); // Collect keys and data // key - // data - | | , value or error KeyCollector.prototype.collect = function (key, data) { if (this.keys.includes(key)) { this.count++; if (data instanceof Error) { this.errs[key] = data; this.emit('error', data, key); } else { this.data[key] = data; } if (this.expected === this.count) { if (this.timer) clearTimeout(this.timer); const errs = this.errs.length ? this.errs : null; this.emit('done', errs, this.data); } } }; KeyCollector.prototype.stop = function () {}; KeyCollector.prototype.pause = function () {}; KeyCollector.prototype.resume = function () {}; module.exports = { DataCollector, KeyCollector }; ================================================ FILE: lib/composition.js ================================================ 'use strict'; function Composition() {} const COMPOSE_CANCELED = 'Metasync: asynchronous composition canceled'; const COMPOSE_TIMEOUT = 'Metasync: asynchronous composition timed out'; // Asynchronous functions composition // Array of functions results in sequential execution: `[f1, f2, f3]` // Double brackets array of functions results // in parallel execution: `[[f1, f2, f3]]` // flow - , callback-last / err-first // // Returns: , composed callback-last / err-first // // Example: // const composed = metasync([f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]); const compose = (flow) => { const comp = (data, callback) => { if (!callback) { if (typeof data === 'function') { callback = data; data = {}; } else { comp.data = data; return comp; } } comp.done = callback; if (comp.canceled) { if (callback) { callback(new Error(COMPOSE_CANCELED)); } return comp; } if (comp.timeout) { comp.timer = setTimeout(() => { comp.timer = null; if (callback) { callback(new Error(COMPOSE_TIMEOUT)); comp.done = null; } }, comp.timeout); } comp.context = data; comp.arrayed = Array.isArray(comp.context); comp.paused = false; if (comp.len === 0) { comp.finalize(); return comp; } if (comp.parallelize) comp.parallel(); else comp.sequential(); return comp; }; const first = flow[0]; const parallelize = flow.length === 1 && Array.isArray(first); const fns = parallelize ? first : flow; comp.fns = fns; comp.parallelize = parallelize; comp.context = null; comp.timeout = 0; comp.timer = null; comp.len = fns.length; comp.canceled = false; comp.paused = true; comp.arrayed = false; comp.done = null; comp.onResume = null; Object.setPrototypeOf(comp, Composition.prototype); return comp; }; Composition.prototype.on = function (name, callback) { if (name === 'resume') { this.onResume = callback; } }; Composition.prototype.finalize = function (err) { if (this.canceled) return; if (this.timer) { clearTimeout(this.timer); this.timer = null; } const callback = this.done; if (callback) { if (this.paused) { this.on('resume', () => { this.done = null; callback(err, this.context); }); } else { this.done = null; callback(err, this.context); } } }; Composition.prototype.collect = function (err, result) { if (this.canceled) return; if (err) { const callback = this.done; if (callback) { this.done = null; callback(err); } return; } if (result !== this.context && result !== undefined) { if (this.arrayed) { this.context.push(result); } else if (typeof result === 'object') { Object.assign(this.context, result); } } }; Composition.prototype.parallel = function () { let counter = 0; const next = (err, result) => { this.collect(err, result); if (++counter === this.len) this.finalize(); }; const fns = this.fns; const len = this.len; const context = this.context; for (let i = 0; i < len; i++) { const fn = fns[i]; const fc = Array.isArray(fn) ? compose(fn) : fn; fc(context, next); } }; Composition.prototype.sequential = function () { let counter = -1; const fns = this.fns; const len = this.len; const context = this.context; const next = (err, result) => { if (this.canceled) return; if (err || result) this.collect(err, result); if (++counter === len) return void this.finalize(); const fn = fns[counter]; const fc = Array.isArray(fn) ? compose(fn) : fn; if (this.paused) { this.on('resume', () => fc(context, next)); } else { fc(context, next); } }; next(); }; Composition.prototype.then = function (fulfill, reject) { if (this.canceled) { reject(new Error(COMPOSE_CANCELED)); return this; } this((err, result) => { if (err) reject(err); else fulfill(result); }); return this; }; // Clone composed Composition.prototype.clone = function () { const fns = this.fns.slice(); const flow = this.parallelize ? [fns] : fns; return compose(flow); }; // Pause execution Composition.prototype.pause = function () { if (this.canceled) return this; this.paused = true; return this; }; // Resume execution Composition.prototype.resume = function () { if (this.canceled) return this; this.paused = false; if (this.onResume) { const callback = this.onResume; this.onResume = null; callback(); } return this; }; // Set timeout // msec - Composition.prototype.timeout = function (msec) { this.timeout = msec; return this; }; // Cancel execution where possible Composition.prototype.cancel = function () { if (this.canceled) return this; this.canceled = true; const callback = this.done; if (callback) { this.done = null; callback(new Error(COMPOSE_CANCELED)); } return this; }; module.exports = { compose, Composition }; ================================================ FILE: lib/control.js ================================================ 'use strict'; const once = (callback) => { let called = false; return (...args) => { if (!called) { called = true; callback(...args); } }; }; const last = (arr) => arr[arr.length - 1]; const { each } = require('./array'); // Executes all asynchronous functions and pass first result to callback // fns - , callback-last / err-first // callback - , on done, err-first const firstOf = (fns, callback) => { const done = once(callback); each(fns, (f, iterCb) => f((...args) => { done(...args); iterCb(...args); }), ); }; // Parallel execution // Signature: fns[, context], callback // fns - , callback-last / err-first // context - , incoming data, optional // callback - , on done, err-first // // Example: // metasync.parallel([f1, f2, f3], (err, data) => {}); const parallel = (fns, context, callback) => { if (!callback) { callback = context; context = {}; } const done = once(callback); const isArray = Array.isArray(context); const len = fns.length; if (len === 0) return void done(null, context); let counter = 0; const finishFn = (fn, err, result) => { if (err) return void done(err); if (result !== context && result !== undefined) { if (isArray) context.push(result); else if (typeof result === 'object') Object.assign(context, result); } if (++counter === len) done(null, context); }; // fn may be array of function for (const fn of fns) { const finish = finishFn.bind(null, fn); if (fn.length === 2) fn(context, finish); else fn(finish); } }; // Sequential execution // Signature: fns[, context], callback // fns - , callback-last with err-first contract // context - , incoming data, optional // callback - , err-first on done // // Example: // metasync.sequential([f1, f2, f3], (err, data) => {}); const sequential = (fns, context, callback) => { if (!callback) { callback = context; context = {}; } const done = once(callback); const isArray = Array.isArray(context); const len = fns.length; if (len === 0) return void done(null, context); let i = -1; const next = () => { let fn = null; const finish = (err, result) => { if (result !== context && result !== undefined) { if (isArray) context.push(result); else if (typeof result === 'object') Object.assign(context, result); } if (err) return void done(err); next(); }; if (++i === len) return void done(null, context); fn = fns[i]; if (fn.length === 2) fn(context, finish); else fn(finish); }; next(); }; // Run `asyncFn` if `condition` is truthy, else return `defaultVal` to callback. // Signature: condition[, defaultVal], asyncFn, ...args // condition - // defaultVal - , optional, value that will be returned to callback if // `condition` is falsy. // asyncFn - , callback-last function that will be executed if // `condition` if truthy // args - , args to pass to `asyncFn` const runIf = (condition, defaultVal, asyncFn, ...args) => { if (typeof defaultVal === 'function') { args.unshift(asyncFn); asyncFn = defaultVal; defaultVal = undefined; } if (condition) { asyncFn(...args); } else { const callback = last(args); process.nextTick(callback, null, defaultVal); } }; // Run `asyncFn` if it is provided // Signature: asyncFn, ...args // asyncFn - , callback-last function that will be executed if it // is provided // args - , args to pass to `asyncFn` const runIfFn = (asyncFn, ...args) => { runIf(asyncFn, undefined, asyncFn, ...args); }; module.exports = { firstOf, parallel, sequential, runIf, runIfFn, }; ================================================ FILE: lib/do.js ================================================ 'use strict'; function Do() {} const chain = function (fn, ...args) { const current = (done) => { if (done) current.done = done; if (current.prev) { current.prev.next = current; current.prev(); } else { current.forward(); } return current; }; const prev = this instanceof Do ? this : null; const fields = { prev, fn, args, done: null }; Object.setPrototypeOf(current, Do.prototype); return Object.assign(current, fields); }; Do.prototype.do = function (fn, ...args) { return chain.call(this, fn, ...args); }; Do.prototype.forward = function () { if (this.fn) { this.fn(...this.args, (err, data) => { const next = this.next; if (next) { if (next.fn) next.forward(); } else if (this.done) { this.done(err, data); } }); } }; module.exports = { do: chain }; ================================================ FILE: lib/fp.js ================================================ 'use strict'; let asyncChainMethods = null; // Convert synchronous function to asynchronous // Transform function with args arguments and callback // to function with args as separate values and callback // fn - , callback-last / err-first // // Returns: const toAsync = (fn) => (...argsCb) => { const len = argsCb.length - 1; const callback = argsCb[len]; const args = argsCb.slice(0, len); return fn(args, callback); }; // Wrap function adding async chain methods // fn - , asynchronous // args - , its arguments const asAsync = (fn, ...args) => { const wrapped = fn.bind(null, ...args); for (const name in asyncChainMethods) { const method = asyncChainMethods[name]; wrapped[name] = (...args) => asAsync(method(wrapped, ...args)); } return wrapped; }; // Applicative f => a -> f a // args - const of = (...args) => asAsync((callback) => callback(null, ...args)); // Monoid m => a -> a -> a // fn1 - // fn2 - const concat = (fn1, fn2) => toAsync((args1, callback) => fn1(...args1, (err, ...args2) => { if (err !== null) callback(err); else fn2(...args2, callback); }), ); // Functor f => (a -> b) -> f a -> f b // fn1 - // f - const fmap = (fn1, f) => { const fn2 = toAsync((args, callback) => of(f(...args))(callback)); return concat(fn1, fn2); }; // Applicative f => f (a -> b) -> f a -> f b // fn - // funcA - const ap = (fn, funcA) => concat(funcA, (f, callback) => fmap(fn, f)(callback)); asyncChainMethods = { fmap, ap, concat }; module.exports = { toAsync, asAsync, of, concat, fmap, ap, }; ================================================ FILE: lib/memoize.js ================================================ 'use strict'; function Memoized() {} // Create memoized function // fn - , sync or async // // Returns: , memoized const memoize = (fn) => { const cache = new Map(); const memoized = function (...args) { const callback = args.pop(); const key = args[0]; const record = cache.get(key); if (record) return void callback(record.err, record.data); fn(...args, (err, data) => { memoized.add(key, err, data); memoized.emit('memoize', key, err, data); callback(err, data); }); }; const fields = { cache, timeout: 0, limit: 0, size: 0, maxSize: 0, maxCount: 0, events: { timeout: null, memoize: null, overflow: null, add: null, del: null, clear: null, }, }; Object.setPrototypeOf(memoized, Memoized.prototype); return Object.assign(memoized, fields); }; Memoized.prototype.clear = function () { this.emit('clear'); this.cache.clear(); }; Memoized.prototype.add = function (key, err, data) { this.emit('add', err, data); this.cache.set(key, { err, data }); return this; }; Memoized.prototype.del = function (key) { this.emit('del', key); this.cache.delete(key); return this; }; Memoized.prototype.get = function (key, callback) { const record = this.cache.get(key); callback(record.err, record.data); return this; }; // Add event listener // eventName - // listener - , handler // // Example: // const memoized = new Memoized(); // memoized.on('memoize', (err, data) => { ... }); // memoized.on('add', (key, err, data) => { ... }); // memoized.on('del', (key) => { ... }) // memoized.on('clear', () => { ... }) Memoized.prototype.on = function (eventName, listener) { if (eventName in this.events) { this.events[eventName] = listener; } }; // Emit Memoized events // eventName - // args - , rest arguments Memoized.prototype.emit = function (eventName, ...args) { const event = this.events[eventName]; if (event) event(...args); }; module.exports = { memoize, Memoized }; ================================================ FILE: lib/poolify.js ================================================ 'use strict'; const duplicate = (factory, n) => Array.from({ length: n }, factory); const provide = (callback) => (item) => { setImmediate(() => { callback(item); }); }; const poolify = (factory, min, norm, max) => { let allocated = norm; const pool = (par) => { if (Array.isArray(par)) { while (par.length) { const item = par.shift(); const delayed = pool.delayed.shift(); if (delayed) delayed(item); else pool.items.push(item); } return pool; } if (pool.items.length < min && allocated < max) { const grow = Math.min(max - allocated, norm - pool.items.length); allocated += grow; const items = duplicate(factory, grow); pool.items.push(...items); } const res = pool.items.pop(); if (!par) return res; const callback = provide(par); if (res) callback(res); else pool.delayed.push(callback); return pool; }; return Object.assign(pool, { items: duplicate(factory, norm), delayed: [], }); }; module.exports = { poolify }; ================================================ FILE: lib/poolify.opt.js ================================================ 'use strict'; const duplicate = (factory, n) => Array.from({ length: n }, factory); const provide = (callback) => (item) => { setImmediate(() => { callback(item); }); }; const poolify = (factory, min, norm, max) => { let allocated = norm; const items = duplicate(factory, norm); const delayed = []; const pool = (par) => { if (Array.isArray(par)) { while (par.length) { const item = par.shift(); const request = delayed.shift(); if (request) request(item); else items.push(item); } return pool; } if (items.length < min && allocated < max) { const grow = Math.min(max - allocated, norm - items.length); allocated += grow; const instances = duplicate(factory, grow); items.push(...instances); } const res = items.pop(); if (!par) return res; const callback = provide(par); if (res) callback(res); else delayed.push(callback); return pool; }; return pool; }; module.exports = { poolify }; ================================================ FILE: lib/poolify.symbol.js ================================================ 'use strict'; const poolified = Symbol('poolified'); const mixFlag = { [poolified]: true }; const duplicate = (factory, n) => Array.from({ length: n }, factory).map((instance) => Object.assign(instance, mixFlag), ); const provide = (callback) => (item) => { setImmediate(() => { callback(item); }); }; const poolify = (factory, min, norm, max) => { let allocated = norm; const pool = (par) => { if (par && par[poolified]) { const delayed = pool.delayed.shift(); if (delayed) delayed(par); else pool.items.push(par); return pool; } if (pool.items.length < min && allocated < max) { const grow = Math.min(max - allocated, norm - pool.items.length); allocated += grow; const items = duplicate(factory, grow); pool.items.push(...items); } const res = pool.items.pop(); if (!par) return res; const callback = provide(par); if (res) callback(res); else pool.delayed.push(callback); return pool; }; return Object.assign(pool, { items: duplicate(factory, norm), delayed: [], }); }; module.exports = { poolify }; ================================================ FILE: lib/queue.js ================================================ 'use strict'; // Queue constructor // concurrency - , asynchronous concurrency function Queue(concurrency) { this.paused = false; this.concurrency = concurrency; this.waitTimeout = 0; this.processTimeout = 0; this.throttleCount = 0; this.throttleInterval = 1000; this.count = 0; this.tasks = []; this.waiting = []; this.factors = {}; this.fifoMode = true; this.roundRobinMode = false; this.priorityMode = false; this.onProcess = null; this.onDone = null; this.onSuccess = null; this.onTimeout = null; this.onFailure = null; this.onDrain = null; } const QUEUE_TIMEOUT = 'Metasync: Queue timed out'; // Set wait before processing timeout // msec - , wait timeout for single item // // Returns: Queue.prototype.wait = function (msec) { this.waitTimeout = msec; return this; }; // Throttle to limit throughput // Signature: count[, interval] // count - , item count // interval - , per interval, optional // default: 1000 msec // // Returns: Queue.prototype.throttle = function (count, interval = 1000) { this.throttleCount = count; this.throttleInterval = interval; return this; }; // Add item to queue // Signature: item[, factor[, priority]] // item - , to be added // factor - | , type, source, // destination or path, optional // priority - , optional // // Returns: Queue.prototype.add = function (item, factor = 0, priority = 0) { if (this.priorityMode && !this.roundRobinMode) { priority = factor; factor = 0; } const task = [item, factor, priority]; const slot = this.count < this.concurrency; if (!this.paused && slot && this.onProcess) { this.next(task); return this; } let tasks; if (this.roundRobinMode) { tasks = this.factors[factor]; if (!tasks) { tasks = []; this.factors[factor] = tasks; this.waiting.push(tasks); } } else { tasks = this.tasks; } if (this.fifoMode) tasks.push(task); else tasks.unshift(task); if (this.priorityMode) { if (this.fifoMode) { tasks.sort((a, b) => b[2] - a[2]); } else { tasks.sort((a, b) => a[2] - b[2]); } } return this; }; // Process next item // task - , next task [item, factor, priority] // // Returns: Queue.prototype.next = function (task) { const item = task[0]; let timer; this.count++; if (this.processTimeout) { timer = setTimeout(() => { const err = new Error(QUEUE_TIMEOUT); if (this.onTimeout) this.onTimeout(err); }, this.processTimeout); } this.onProcess(item, (err, result) => { if (this.onDone) this.onDone(err, result); if (err) { if (this.onFailure) this.onFailure(err); } else if (this.onSuccess) { this.onSuccess(result); } if (timer) { clearTimeout(timer); timer = null; } this.count--; if (this.tasks.length > 0 || this.waiting.length > 0) { this.takeNext(); } else if (this.count === 0 && this.onDrain) { this.onDrain(); } }); return this; }; // Prepare next item for processing // // Returns: Queue.prototype.takeNext = function () { if (this.paused || !this.onProcess) { return this; } let tasks; if (this.roundRobinMode) { tasks = this.waiting.shift(); if (tasks.length > 1) { this.waiting.push(tasks); } } else { tasks = this.tasks; } const task = tasks.shift(); if (task) this.next(task); return this; }; // Pause queue // This function is not completely implemented yet // // Returns: Queue.prototype.pause = function () { this.paused = true; return this; }; // Resume queue // This function is not completely implemented yet // // Returns: Queue.prototype.resume = function () { this.paused = false; return this; }; // Clear queue // // Returns: Queue.prototype.clear = function () { this.count = 0; this.tasks = []; this.waiting = []; this.factors = {}; return this; }; // Set timeout interval and listener // msec - , process timeout for single item // onTimeout - // // Returns: Queue.prototype.timeout = function (msec, onTimeout = null) { this.processTimeout = msec; if (onTimeout) this.onTimeout = onTimeout; return this; }; // Set processing function // fn - // item - // callback - // err - | // result - // // Returns: Queue.prototype.process = function (fn) { this.onProcess = fn; return this; }; // Set listener on processing done // fn - , done listener // err - | // result - // // Returns: Queue.prototype.done = function (fn) { this.onDone = fn; return this; }; // Set listener on processing success // listener - , on success // item - // // Returns: Queue.prototype.success = function (listener) { this.onSuccess = listener; return this; }; // Set listener on processing error // listener - , on failure // err - | // // Returns: Queue.prototype.failure = function (listener) { this.onFailure = listener; return this; }; // Set listener on drain Queue // listener - , on drain // // Returns: Queue.prototype.drain = function (listener) { this.onDrain = listener; return this; }; // Switch to FIFO mode (default for Queue) // // Returns: Queue.prototype.fifo = function () { this.fifoMode = true; return this; }; // Switch to LIFO mode // // Returns: Queue.prototype.lifo = function () { this.fifoMode = false; return this; }; // Activate or deactivate priority mode // flag - , default: true, false will // disable priority mode // // Returns: Queue.prototype.priority = function (flag = true) { this.priorityMode = flag; return this; }; // Activate or deactivate round robin mode // flag - , default: true, false will // disable roundRobin mode // // Returns: Queue.prototype.roundRobin = function (flag = true) { this.roundRobinMode = flag; return this; }; // Pipe processed items to different queue // dest - , destination queue // // Returns: Queue.prototype.pipe = function (dest) { if (dest instanceof Queue) { this.success((item) => { dest.add(item); }); } return this; }; // Create Queue instance // concurrency - , simultaneous and // asynchronously executing tasks // // Returns: const queue = (concurrency) => new Queue(concurrency); module.exports = { queue, Queue }; ================================================ FILE: lib/throttle.js ================================================ 'use strict'; // Get throttling function, executed once per interval // Signature: timeout, fn, ...args // timeout - , msec interval // fn - , to be throttled // args - , arguments for fn, optional // // Returns: const throttle = (timeout, fn, ...args) => { let timer; let wait = false; const execute = args ? (...pars) => (pars ? fn(...args, ...pars) : fn(...args)) : (...pars) => (pars ? fn(...pars) : fn()); const delayed = (...pars) => { timer = undefined; if (wait) execute(...pars); }; const throttled = (...pars) => { if (!timer) { timer = setTimeout(delayed, timeout, ...pars); wait = false; execute(...pars); } wait = true; }; return throttled; }; // Debounce function, delayed execution // Signature: timeout, fn, ...args // timeout - , msec // fn - , to be debounced // args - , arguments for fn, optional const debounce = (timeout, fn, ...args) => { let timer; const debounced = () => (args ? fn(...args) : fn()); const wrapped = () => { if (timer) clearTimeout(timer); timer = setTimeout(debounced, timeout); }; return wrapped; }; const FN_TIMEOUT = 'Metasync: asynchronous function timed out'; // Set timeout for asynchronous function execution // timeout - , time interval // fn - , to be executed // callback - , callback(...args), on done // args - const timeout = (timeout, fn, callback) => { let finished = false; const timer = setTimeout(() => { finished = true; callback(new Error(FN_TIMEOUT)); }, timeout); fn((...args) => { if (!finished) { clearTimeout(timer); finished = true; callback(...args); } }); }; module.exports = { throttle, debounce, timeout, }; ================================================ FILE: metasync.d.ts ================================================ type Callback = (err: Error | null, data?: T) => void; type AsyncFunction = ( data: T, callback: Callback, ) => void; type AsyncFunctionNoData = (callback: Callback) => void; type FlowFunction = AsyncFunction | AsyncFunctionNoData | Flow; type Flow = FlowFunction[]; export interface Composition { (data: unknown, callback?: Callback): Composition; (callback: Callback): Composition; data?: unknown; done?: Callback | null; context?: unknown; timeout?: number; timer?: NodeJS.Timeout | null; canceled?: boolean; paused?: boolean; onResume?: (() => void) | null; fns: FlowFunction[]; parallelize: boolean; len: number; arrayed: boolean; on(name: 'resume', callback: () => void): void; finalize(err?: Error): void; collect(err: Error | null, result?: unknown): void; parallel(): void; sequential(): void; then( fulfill: (result: unknown) => void, reject: (err: Error) => void, ): Composition; clone(): Composition; pause(): Composition; resume(): Composition; timeout(msec: number): Composition; cancel(): Composition; } export function compose(flow: Flow): Composition; export class Composition { constructor(); } export function callbackify Promise>( fn: T, ): (...args: Parameters) => void; export function asyncify unknown>( fn: T, ): (...args: [...Parameters, Callback>]) => void; export function promiseToCallbackLast( promise: Promise, ): (callback: Callback) => void; export function promisify void>( fn: T, ): ( ...args: Parameters extends [...infer A, Callback] ? A : never ) => Promise; export function promisifySync unknown>( fn: T, ): (...args: Parameters) => Promise>; export interface AsyncMapOptions { min?: number; percent?: number; } export function map( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function asyncMap( items: T[], fn: (item: T, index: number) => R, options?: AsyncMapOptions | Callback, done?: Callback, ): void; export function filter( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function reduce( items: T[], fn: ( previous: R, current: T, callback: Callback, counter: number, items: T[], ) => void, done: Callback, initial?: R, ): void; export function reduceRight( items: T[], fn: ( previous: R, current: T, callback: Callback, counter: number, items: T[], ) => void, done: Callback, initial?: R, ): void; export function each( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function series( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function find( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function every( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export function some( items: T[], fn: (item: T, callback: Callback) => void, done: Callback, ): void; export interface Collector { expectKeys: Set | null; expected: number; keys: Set; count: number; timer: NodeJS.Timeout | null; onDone: (err: Error | null, data?: unknown) => void; isDistinct: boolean; isDone: boolean; data: Record; collect(key: string, err: Error | null, value?: unknown): Collector; pick(key: string, value: unknown): Collector; fail(key: string, err: Error): Collector; take( key: string, fn: (...args: unknown[]) => void, ...args: unknown[] ): Collector; timeout(msec: number): Collector; done(callback: (err: Error | null, data?: unknown) => void): Collector; finalize(key: Error | null, err?: Error, data?: unknown): Collector; distinct(value?: boolean): Collector; cancel(err?: Error): Collector; then( fulfill: (result: unknown) => void, reject: (err: Error) => void, ): Collector; } export function collect(expected: number | string[]): Collector; export class Collector { constructor(expected: number | string[]); } export function firstOf(fns: AsyncFunctionNoData[], callback: Callback): void; export function parallel( fns: FlowFunction[], context?: unknown, callback?: Callback, ): void; export function parallel(fns: FlowFunction[], callback: Callback): void; export function sequential( fns: FlowFunction[], context?: unknown, callback?: Callback, ): void; export function sequential(fns: FlowFunction[], callback: Callback): void; export function runIf( condition: unknown, defaultVal: unknown, asyncFn: (...args: unknown[]) => void, ...args: unknown[] ): void; export function runIf( condition: unknown, asyncFn: (...args: unknown[]) => void, ...args: unknown[] ): void; export function runIfFn( asyncFn: ((...args: unknown[]) => void) | undefined, ...args: unknown[] ): void; export interface Do { prev: Do | null; fn: ((...args: unknown[]) => void) | null; args: unknown[]; done: Callback | null; next: Do | null; (done?: Callback): Do; do(fn: (...args: unknown[]) => void, ...args: unknown[]): Do; forward(): void; } export function doFn(fn: (...args: unknown[]) => void, ...args: unknown[]): Do; export { doFn as do }; export function toAsync void>( fn: T, ): (...argsCb: [...unknown[], Callback]) => void; export function asAsync void>( fn: T, ...args: Parameters extends [...infer A, Callback] ? A : never ): (...args: unknown[]) => void & { fmap: typeof fmap; ap: typeof ap; concat: typeof concat; }; export function of(...args: unknown[]): (...args: unknown[]) => void & { fmap: typeof fmap; ap: typeof ap; concat: typeof concat; }; export function concat( fn1: (...args: unknown[]) => void, fn2: (...args: unknown[]) => void, ): (...args: unknown[]) => void; export function fmap( fn1: (...args: unknown[]) => void, f: (...args: unknown[]) => unknown, ): (...args: unknown[]) => void; export function ap( fn: (...args: unknown[]) => void, funcA: (...args: unknown[]) => void, ): (...args: unknown[]) => void; export interface MemoizedEvents { timeout: ((key: unknown) => void) | null; memoize: ((key: unknown, err: Error | null, data?: unknown) => void) | null; overflow: ((key: unknown) => void) | null; add: ((err: Error | null, data?: unknown) => void) | null; del: ((key: unknown) => void) | null; clear: (() => void) | null; } export interface Memoized extends Function { cache: Map; timeout: number; limit: number; size: number; maxSize: number; maxCount: number; events: MemoizedEvents; (...args: [...unknown[], Callback]): void; clear(): Memoized; add(key: unknown, err: Error | null, data?: unknown): Memoized; del(key: unknown): Memoized; get(key: unknown, callback: Callback): Memoized; on( eventName: keyof MemoizedEvents, listener: (...args: unknown[]) => void, ): void; emit(eventName: keyof MemoizedEvents, ...args: unknown[]): void; } export function memoize void>( fn: T, ): Memoized; export class Memoized { constructor(); } export interface Pool { (par?: Callback | unknown[]): Pool | unknown; items: unknown[]; delayed: Array<(item: unknown) => void>; } export function poolify( factory: () => unknown, min: number, norm: number, max: number, ): Pool; export interface Queue { paused: boolean; concurrency: number; waitTimeout: number; processTimeout: number; throttleCount: number; throttleInterval: number; count: number; tasks: Array<[unknown, number | string, number]>; waiting: Array>; factors: Record>; fifoMode: boolean; roundRobinMode: boolean; priorityMode: boolean; onProcess: ((item: unknown, callback: Callback) => void) | null; onDone: ((err: Error | null, result?: unknown) => void) | null; onSuccess: ((item: unknown) => void) | null; onTimeout: ((err: Error) => void) | null; onFailure: ((err: Error) => void) | null; onDrain: (() => void) | null; wait(msec: number): Queue; throttle(count: number, interval?: number): Queue; add(item: unknown, factor?: number | string, priority?: number): Queue; next(task: [unknown, number | string, number]): Queue; takeNext(): Queue; pause(): Queue; resume(): Queue; clear(): Queue; timeout(msec: number, onTimeout?: ((err: Error) => void) | null): Queue; process(fn: (item: unknown, callback: Callback) => void): Queue; done(fn: (err: Error | null, result?: unknown) => void): Queue; success(listener: (item: unknown) => void): Queue; failure(listener: (err: Error) => void): Queue; drain(listener: () => void): Queue; fifo(): Queue; lifo(): Queue; priority(flag?: boolean): Queue; roundRobin(flag?: boolean): Queue; pipe(dest: Queue): Queue; } export function queue(concurrency: number): Queue; export class Queue { constructor(concurrency: number); } export function throttle void>( timeout: number, fn: T, ...args: unknown[] ): (...pars: unknown[]) => void; export function debounce void>( timeout: number, fn: T, ...args: unknown[] ): () => void; export function timeout( timeout: number, fn: (callback: Callback) => void, callback: Callback, ): void; export interface AsyncIteratorResult { done: boolean; value: T; } export interface AsyncIterator { base: Iterator | AsyncIterator; [Symbol.asyncIterator](): AsyncIterator; next(): Promise>; count(): Promise; each( fn: (value: T) => void | Promise, thisArg?: unknown, ): Promise; forEach( fn: (value: T) => void | Promise, thisArg?: unknown, ): Promise; parallel( fn: (value: T) => unknown | Promise, thisArg?: unknown, ): Promise; every( predicate: (value: T) => boolean | Promise, thisArg?: unknown, ): Promise; find( predicate: (value: T) => boolean | Promise, thisArg?: unknown, ): Promise; includes(element: T): Promise; reduce( reducer: (accumulator: R, value: T) => R | Promise, initialValue: R, ): Promise; reduce(reducer: (accumulator: R, value: T) => R | Promise): Promise; some( predicate: (value: T) => boolean | Promise, thisArg?: unknown, ): Promise; someCount( predicate: (value: T) => boolean | Promise, count: number, thisArg?: unknown, ): Promise; collectTo unknown>( CollectionClass: C, ): Promise>; collectWith( obj: unknown, collector: (obj: unknown, element: T) => void, ): Promise; join(sep?: string, prefix?: string, suffix?: string): Promise; toArray(): Promise; map(fn: (value: T) => R | Promise, thisArg?: unknown): AsyncIterator; filter( predicate: (value: T) => boolean | Promise, thisArg?: unknown, ): AsyncIterator; flat(depth?: number): AsyncIterator; flatMap( fn: (value: T) => R | Promise | Iterable | AsyncIterable, thisArg?: unknown, ): AsyncIterator; take(count: number): AsyncIterator; takeWhile( predicate: (value: T) => boolean | Promise, thisArg?: unknown, ): AsyncIterator; skip(amount: number): AsyncIterator; zip( ...iterators: { [K in keyof U]: Iterable | AsyncIterable } ): AsyncIterator<[T, ...U]>; chain( ...iterators: { [K in keyof U]: Iterable | AsyncIterable } ): AsyncIterator; enumerate(): AsyncIterator<[number, T]>; throttle(percent?: number, min?: number): AsyncIterator; } export function asyncIter( base: Iterable | AsyncIterable, ): AsyncIterator; export class AsyncIterator { constructor(base: Iterable | AsyncIterable); } ================================================ FILE: metasync.js ================================================ 'use strict'; const composition = require('./lib/composition.js'); const adapters = require('./lib/adapters.js'); const array = require('./lib/array.js'); const collector = require('./lib/collector.js'); const control = require('./lib/control.js'); const doModule = require('./lib/do.js'); const fp = require('./lib/fp.js'); const memoize = require('./lib/memoize.js'); const poolify = require('./lib/poolify.js'); const queue = require('./lib/queue.js'); const throttle = require('./lib/throttle.js'); const asyncIterator = require('./lib/async-iterator.js'); const { compose } = composition; const submodules = { ...composition, // Unified abstraction ...adapters, // Adapters to convert different async contracts ...array, // Array utilities ...collector, // DataCollector and KeyCollector ...control, // Control flow utilities ...doModule, // Simple chain/do ...fp, // Async utils for functional programming ...memoize, // Async memoization ...poolify, // Create pool from factory ...queue, // Concurrent queue ...throttle, // Throttling utilities ...asyncIterator, // AsyncIterator utilities }; module.exports = Object.assign(compose, submodules); ================================================ FILE: package.json ================================================ { "name": "metasync", "version": "0.3.33", "author": "Timur Shemsedinov ", "description": "Asynchronous Programming Library", "license": "MIT", "keywords": [ "metasync", "callback", "promise", "async", "asyncronous", "parallel", "sequential", "metarhia", "flow", "collector", "errback", "err-first", "error-first", "callback-last", "throttle", "impress", "datacollector", "keycollector", "composition" ], "repository": { "type": "git", "url": "https://github.com/metarhia/metasync" }, "main": "metasync.js", "browser": { "metasync.js": "dist/metasync.js" }, "files": [ "dist/", "lib/" ], "readmeFilename": "README.md", "scripts": { "test": "npm run lint && metatests test/ && metatests tests/async-iterator.js", "perf": "tests/load/run.sh", "lint": "eslint . && prettier --check \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/*.ts\"", "fix": "eslint . --fix && prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/*.ts\"", "types": "tsc -p tsconfig.json" }, "engines": { "node": ">=14.0.0" }, "dependencies": { "metautil": "^5.3.0" }, "devDependencies": { "@types/node": "^25.0.3", "eslint": "^9.39.2", "eslint-config-metarhia": "^9.1.5", "prettier": "^3.7.4", "metatests": "^0.9.3", "typescript": "^5.9.3" } } ================================================ FILE: prettier.config.js ================================================ 'use strict'; module.exports = { printWidth: 80, singleQuote: true, trailingComma: 'all', tabWidth: 2, useTabs: false, semi: true, }; ================================================ FILE: test/adapters.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('callbackify: Promise to callback-last', (test) => { const promiseReturning = () => Promise.resolve('result'); const asyncFn = metasync.callbackify(promiseReturning); asyncFn((err, value) => { if (err) { test.error(err, 'must not throw'); } test.strictSame(value, 'result'); test.end(); }); }); metatests.test('asyncify: sync function to callback-last', (test) => { const fn = (par) => par; const asyncFn = metasync.asyncify(fn); asyncFn('result', (err, value) => { if (err) { test.error(err, 'must not throw'); } test.strictSame(value, 'result'); test.end(); }); }); metatests.test('promisify: callback-last to Promise', (test) => { const id = 100; const data = { key: 'value' }; const getDataAsync = (dataId, callback) => { test.strictSame(dataId, id); callback(null, data); }; const getDataPromise = metasync.promisify(getDataAsync); getDataPromise(id) .then((result) => { test.strictSame(result, data); test.end(); }) .catch((err) => { test.error(err, 'must not throw'); }); }); metatests.test('promisify: callback-last to Promise throw', (test) => { const id = 100; const getDataAsync = (dataId, callback) => { test.strictSame(dataId, id); callback(new Error('Data not found')); }; const getDataPromise = metasync.promisify(getDataAsync); getDataPromise(id) .then((result) => { test.notOk(result); }) .catch((err) => { test.ok(err); test.end(); }); }); metatests.test('promisify: sync function to Promise', (test) => { const id = 100; const data = { key: 'value' }; const getDataSync = (dataId) => { test.strictSame(dataId, id); return data; }; const getDataPromise = metasync.promisifySync(getDataSync); getDataPromise(id) .then((result) => { test.strictSame(result, data); test.end(); }) .catch((err) => { test.error(err, 'must not throw'); }); }); metatests.test('promisify: sync to Promise throw', (test) => { const id = 100; const getDataSync = (dataId) => { test.strictSame(dataId, id); throw new Error('Data not found'); }; const getDataPromise = metasync.promisifySync(getDataSync); getDataPromise(id) .then((result) => { test.notOk(result); }) .catch((err) => { test.ok(err); test.end(); }); }); metatests.test('promiseToCallbackLast: Promise to callback-last', (test) => { const promise = Promise.resolve('result'); const asyncFn = metasync.promiseToCallbackLast(promise); asyncFn((err, value) => { if (err) { test.error(err, 'must not throw'); } test.strictSame(value, 'result'); test.end(); }); }); ================================================ FILE: test/array.asyncMap.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('succesfull map', (test) => { test.plan(2); const arr = [1, 2, 3]; const expectedArr = [2, 4, 6]; metasync.asyncMap( arr, (item) => item * 2, (err, newArr) => { test.error(err); test.strictSame(newArr, expectedArr); }, ); }); const doSmth = (time) => { const begin = Date.now(); while (Date.now() - begin < time); }; metatests.test('Non-blocking', (test) => { const ITEM_TIME = 1; const TIMER_TIME = 9; const ARRAY_SIZE = 1000; const EXPECTED_PERCENT = 0.5; const EXPECTED_DEVIATION = 0.4; const arr = new Array(ARRAY_SIZE).fill(1); const timer = setInterval(() => doSmth(TIMER_TIME), 1); const begin = Date.now(); metasync.asyncMap( arr, () => doSmth(ITEM_TIME), { percent: EXPECTED_PERCENT }, () => { clearInterval(timer); const mapTime = ITEM_TIME * ARRAY_SIZE; const allTime = Date.now() - begin; const actualPercent = mapTime / allTime; const actualDeviation = Math.abs(actualPercent - EXPECTED_PERCENT); test.assert(actualDeviation <= EXPECTED_DEVIATION); test.end(); }, ); }); ================================================ FILE: test/array.each.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('successful each', (test) => { const arr = [1, 2, 3, 4]; const elementsSet = new Set(); const expectedElementsSet = new Set(arr); metasync.each( arr, (el, callback) => process.nextTick(() => { elementsSet.add(el); callback(null); }), (err) => { test.error(err); test.strictSame(elementsSet, expectedElementsSet); test.end(); }, ); }); metatests.test('each with empty array', (test) => { const arr = []; const elementsSet = new Set(); const expectedElementsSet = new Set(arr); metasync.each( arr, (el, callback) => process.nextTick(() => { elementsSet.add(el); callback(null); }), (err) => { test.error(err); test.strictSame(elementsSet, expectedElementsSet); test.end(); }, ); }); metatests.test('each with error', (test) => { const arr = [1, 2, 3, 4]; let count = 0; const elementsSet = new Set(); const expectedElementsCount = 2; const eachError = new Error('Each error'); metasync.each( arr, (el, callback) => process.nextTick(() => { elementsSet.add(el); count++; if (count === expectedElementsCount) { callback(eachError); } else { callback(null); } }), (err) => { test.strictSame(err, eachError); test.strictSame(elementsSet.size, expectedElementsCount); test.end(); }, ); }); ================================================ FILE: test/array.every.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const identity = (x, callback) => callback(null, x); const strictSameResult = (input, expectedResult, test, done) => { metasync.every(input, identity, (err, result) => { test.error(err); test.strictSame(result, expectedResult); done(); }); }; const fewStrictSameResult = (inOutPairs, test) => { let i = 0; const testsEnd = metasync.collect(inOutPairs.length); testsEnd.done(() => test.end()); const cb = () => testsEnd.pick('item' + i++); for (const [input, output] of inOutPairs) { strictSameResult(input, output, test, cb); } }; metatests.test('every with error', (test) => { const data = [1, 2, 3]; const everyErr = new Error('Every error'); const predicate = (item, callback) => { process.nextTick(() => item % 2 === 0 ? callback(everyErr) : callback(null, true), ); }; metasync.every(data, predicate, (err) => { test.strictSame(err, everyErr); test.end(); }); }); metatests.test('every with empty array', (test) => strictSameResult([], true, test, () => test.end()), ); metatests.test('every with one-element arrays', (test) => fewStrictSameResult( [ [[false], false], [[true], true], ], test, ), ); metatests.test('every with two-element arrays', (test) => fewStrictSameResult( [ [[false, false], false], [[false, true], false], [[true, false], false], [[true, true], true], ], test, ), ); metatests.test('every', (test) => { const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; const predicate = (item, callback) => { process.nextTick(() => callback(null, item > 0)); }; metasync.every(data, predicate, (err, result) => { test.error(err); test.strictSame(result, true); test.end(); }); }); ================================================ FILE: test/array.filter.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('successful filter', (test) => { const arr = [ 'Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua', ]; const expectedArr = [ 'Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'elit', 'sed', 'do', 'ut', 'et', 'magna', ]; metasync.filter( arr, (str, callback) => process.nextTick(() => callback(null, str.length < 6)), (err, res) => { test.error(err); test.same(res.join(), expectedArr.join()); test.end(); }, ); }); metatests.test('filter with empty array', (test) => { const arr = []; const expectedArr = []; metasync.filter( arr, (str, callback) => process.nextTick(() => callback(null, str.length < 6)), (err, res) => { test.error(err); test.strictSame(res, expectedArr); test.end(); }, ); }); metatests.test('successful filter', (test) => { const arr = [ 'Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua', ]; const filterError = new Error('Filter error'); const expectedArr = [ 'Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'elit', 'sed', 'magna', ]; metasync.filter( arr, (str, callback) => process.nextTick(() => { if (str.length === 2) { callback(filterError); return; } callback(null, str.length < 6); }), (err, res) => { test.error(err); test.same(res.join(), expectedArr.join()); test.end(); }, ); }); ================================================ FILE: test/array.find.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('find with error', (test) => { const data = [1, 2, 3]; const expectedErrorMessage = 'Intentional error'; const predicate = (item, callback) => process.nextTick(() => { if (item % 2 === 0) { callback(new Error(expectedErrorMessage)); } else { callback(null, false); } }); metasync.find(data, predicate, (err) => { test.type(err, 'Error', 'err must be an instance of Error'); test.strictSame(err.message, expectedErrorMessage); test.end(); }); }); metatests.test('find', (test) => { const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; const expected = 15; const predicate = (item, callback) => process.nextTick(() => callback(null, item % 3 === 0 && item % 5 === 0)); metasync.find(data, predicate, (err, result) => { test.error(err, 'must not return an error'); test.strictSame(result, expected, `result should be: ${expected}`); test.end(); }); }); metatests.test('with empty array', (test) => { metasync.find( [], (el, callback) => process.nextTick(() => callback(null, true)), (err, result) => { test.error(err); test.strictSame(result, undefined); test.end(); }, ); }); metatests.test('with array without element which is searching', (test) => { const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; metasync.find( data, (el, callback) => process.nextTick(() => callback(null, el === 20)), (err, result) => { test.error(err); test.strictSame(result, undefined); test.end(); }, ); }); ================================================ FILE: test/array.map.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('succesfull map', (test) => { const arr = [1, 2, 3]; const expectedArr = [1, 4, 9]; metasync.map( arr, (x, callback) => process.nextTick(() => callback(null, x * x)), (err, res) => { test.error(err); test.strictSame(res, expectedArr); test.end(); }, ); }); metatests.test('map with empty array', (test) => { const arr = []; const expectedArr = []; metasync.map( arr, (x, callback) => process.nextTick(() => callback(null, x * x)), (err, res) => { test.error(err); test.strictSame(res, expectedArr); test.end(); }, ); }); metatests.test('map with error', (test) => { const arr = [1, 2, 3]; const mapError = new Error('Map error'); let count = 0; metasync.map( arr, (x, callback) => process.nextTick(() => { count++; if (count === 2) { callback(mapError); return; } callback(null, x * x); }), (err, res) => { test.strictSame(err, mapError); test.strictSame(res, undefined); test.end(); }, ); }); ================================================ FILE: test/array.reduce.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('reduce with initial', (test) => { const arr = [1, 2, 3, 4, 5]; const initial = 10; const expectedRes = 25; metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, initial, ); }); metatests.test('reduce with initial and empty array', (test) => { const arr = []; const initial = 10; const expectedRes = 10; metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, initial, ); }); metatests.test('reduce without initial and with empty array', (test) => { const arr = []; const expectedError = new TypeError( 'Metasync: reduce of empty array with no initial value', ); metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.strictSame(err, expectedError); test.strictSame(res, undefined); test.end(); }, ); }); metatests.test('reduce single-element array without initial', (test) => { const arr = [2]; metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.strictSame(err, null); test.strictSame(res, 2); test.end(); }, ); }); metatests.test('reduce without initial', (test) => { const arr = [1, 2, 3, 4, 5]; const expectedRes = 15; metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, ); }); metatests.test('reduce with asymetric function', (test) => { const arr = '10110011'; const expectedRes = 179; metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev * 2 + +cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, ); }); metatests.test('reduce with error', (test) => { const arr = '10120011'; const reduceError = new Error('Reduce error'); metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => { const digit = +cur; if (digit > 1) { callback(reduceError); return; } callback(null, prev * 2 + digit); }), (err, res) => { test.strictSame(err, reduceError); test.strictSame(res, undefined); test.end(); }, ); }); ================================================ FILE: test/array.reduceRight.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('reduceRight with initial', (test) => { const arr = [1, 2, 3, 4, 5]; const initial = 10; const expectedRes = 25; metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, initial, ); }); metatests.test('reduceRight with initial and empty array', (test) => { const arr = []; const initial = 10; const expectedRes = 10; metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, initial, ); }); metatests.test('reduceRight without initial and with empty array', (test) => { const arr = []; const expectedError = new TypeError( 'Metasync: reduceRight of empty array with no initial value', ); metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.strictSame(err, expectedError); test.strictSame(res, undefined); test.end(); }, ); }); metatests.test( 'reduceRight without initial and with single-element array', (test) => { const arr = [2]; metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.strictSame(err, null); test.strictSame(res, 2); test.end(); }, ); }, ); metatests.test('reduceRight without initial', (test) => { const arr = [1, 2, 3, 4, 5]; const expectedRes = 15; metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, ); }); metatests.test('reduceRight with asymetric function', (test) => { const arr = '10110011'; const expectedRes = 205; metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev * 2 + +cur)), (err, res) => { test.error(err); test.strictSame(res, expectedRes); test.end(); }, ); }); metatests.test('reduceRight with error', (test) => { const arr = '10120011'; const reduceError = new Error('Reduce error'); metasync.reduce( arr, (prev, cur, callback) => process.nextTick(() => { const digit = +cur; if (digit > 1) { callback(reduceError); return; } callback(null, prev * 2 + digit); }), (err, res) => { test.strictSame(err, reduceError); test.strictSame(res, undefined); test.end(); }, ); }); ================================================ FILE: test/array.series.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('successful series', (test) => { const arr = [1, 2, 3, 4]; const expectedElements = arr; const elements = []; metasync.series( arr, (el, callback) => { elements.push(el); callback(null); }, (err) => { test.error(err); test.strictSame(elements, expectedElements); test.end(); }, ); }); metatests.test('series with error', (test) => { const arr = [1, 2, 3, 4]; const expectedElements = [1, 2]; const expectedElementsCount = 2; const elements = []; let count = 0; const seriesError = new Error('seriesError'); metasync.series( arr, (el, callback) => { elements.push(el); count++; if (count === expectedElementsCount) { callback(seriesError); } else { callback(null); } }, (err) => { test.strictSame(err, seriesError); test.strictSame(elements, expectedElements); test.end(); }, ); }); ================================================ FILE: test/array.some.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('successful some', (test) => { const arr = [1, 2, 3]; const predicate = (x, callback) => callback(null, x % 2 === 0); metasync.some(arr, predicate, (err, accepted) => { test.error(err); test.strictSame(accepted, true); test.end(); }); }); metatests.test('failing some', (test) => { const arr = [1, 2, 3]; const predicate = (x, callback) => callback(null, x > 3); metasync.some(arr, predicate, (err, accepted) => { test.error(err); test.strictSame(accepted, false); test.end(); }); }); metatests.test('erroneous some', (test) => { const arr = [1, 2, 3]; const someError = new Error('Some error'); const predicate = (x, callback) => x % 2 === 0 ? callback(someError) : callback(null, false); metasync.some(arr, predicate, (err, accepted) => { test.strictSame(err, someError); test.strictSame(accepted, undefined); test.end(); }); }); metatests.test('some with empty array', (test) => { const arr = []; const predicate = (x, callback) => callback(null, x > 3); metasync.some(arr, predicate, (err, accepted) => { test.error(err); test.strictSame(accepted, false); test.end(); }); }); ================================================ FILE: test/collectors.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('data collector', (test) => { const expectedResult = { key1: 1, key2: 2, key3: 3, }; const dc = metasync .collect(3) .done((err, result) => { test.error(err); test.strictSame(result, expectedResult); test.end(); }) .timeout(1000); dc.collect('key1', null, 1); dc.collect('key2', null, 2); dc.collect('key3', null, 3); }); metatests.test('data collector', (test) => { const expectedResult = { key1: 1, key2: 2, key3: 3, }; const kc = metasync .collect(['key1', 'key2', 'key3']) .done((err, result) => { test.error(err); test.strictSame(result, expectedResult); test.end(); }) .timeout(); kc.collect('key1', null, 1); kc.collect('key2', null, 2); kc.collect('key3', null, 3); }); metatests.test('distinct data collector', (test) => { const expectedResult = { key1: 2, key2: 2, key3: 3, }; const dc = metasync .collect(3) .distinct() .done((err, result) => { test.error(err); test.strictSame(result, expectedResult); test.end(); }); dc.pick('key1', 1); dc.pick('key1', 2); dc.pick('key2', 2); dc.pick('key3', 3); }); metatests.test('distinct key collector', (test) => { const expectedResult = { key1: 2, key2: 2, key3: 3, }; const kc = metasync .collect(['key1', 'key2', 'key3']) .distinct() .done((err, result) => { test.error(err); test.strictSame(result, expectedResult); test.end(); }); kc.pick('key1', 1); kc.pick('key1', 2); kc.pick('key2', 2); kc.pick('key3', 3); }); metatests.test('data collector with repeated keys', (test) => { const dc = metasync .collect(3) .timeout(100) .done((err) => { test.assert(err); test.end(); }); dc.collect('key1', null, 1); dc.collect('key1', null, 2); dc.collect('key2', null, 2); }); metatests.test('key collector with repeated keys', (test) => { const kc = metasync .collect(['key1', 'key2', 'key3']) .timeout(100) .done((err) => { test.assert(err); test.end(); }); kc.collect('key1', null, 1); kc.collect('key1', null, 2); kc.collect('key2', null, 2); }); metatests.test('collect with error', (test) => { const testErr = new Error('Test error'); const col = metasync.collect(1); col.done((err, res) => { test.strictSame(err, testErr); test.strictSame(res, {}); test.end(); }); col.fail('someKey', testErr); }); metatests.test(`collect method calling after it's done`, (test) => { const col = metasync.collect(1); col.done((err, res) => { test.error(err); test.strictSame(res, { someKey: 'someVal' }); test.end(); }); col.pick('someKey', 'someVal'); col.pick('someKey2', 'someVal2'); }); metatests.test('keys collector receives wrong key', (test) => { const col = metasync.collect(['rightKey']); col.done((err, res) => { test.error(err); test.strictSame(res, { wrongKey: 'someVal', rightKey: 'someVal' }); test.end(); }); col.pick('wrongKey', 'someVal'); col.pick('rightKey', 'someVal'); }); metatests.test('distinct keys collector receives wrong key', (test) => { const col = metasync.collect(['rightKey']).distinct(); col.done((err) => { test.assert(err); test.end(); }); col.pick('wrongKey', 'someVal'); col.pick('rightKey', 'someVal'); }); metatests.test('collect with take', (test) => { const col = metasync.collect(1); col.done((err, res) => { test.error(err); test.strictSame(res, { someKey: 'someVal' }); test.end(); }); const af = (x, callback) => callback(null, x); col.take('someKey', af, 'someVal'); }); metatests.test('collect with timeout error', (test) => { const timeoutErr = new Error('Metasync: Collector timed out'); const col = metasync .collect(1) .done((err, res) => { test.strictSame(err, timeoutErr); test.strictSame(res, {}); test.end(); }) .timeout(1); const af = (x, callback) => setTimeout(() => callback(null, x), 2); col.take('someKey', af, 'someVal'); }); metatests.test('collect with take calls bigger than expected', (test) => { const col = metasync.collect(1).done((err, res) => { test.error(err); test.strictSame(Object.keys(res).length, 1); test.end(); }); const af = (x, callback) => setTimeout(() => callback(null, x), 1); col.take('someKey', af, 'someVal'); col.take('someKey2', af, 'someVal2'); }); metatests.test('cancel data collector', (test) => { const dc = metasync.collect(3).done((err) => { test.assert(err); test.end(); }); dc.pick('key', 'value'); dc.cancel(); }); metatests.test('cancel key collector', (test) => { const dc = metasync.collect(['uno', 'due']).done((err) => { test.assert(err); test.end(); }); dc.pick('key', 'value'); dc.cancel(); }); metatests.test('collect then success', (test) => { const col = metasync.collect(1).then( (result) => { test.assert(result); test.end(); }, (err) => { test.error(err); test.end(); }, ); col.pick('Key', 'value'); }); metatests.test('collect then fail', (test) => { metasync .collect(5) .timeout(10) .then( (result) => { test.error(result); test.end(); }, (err) => { test.assert(err); test.end(); }, ); }); ================================================ FILE: test/compose.clone.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('async functions composition clone', (test) => { const data = { test: 'data' }; const expectedData = { test: 'data', data1: 'data 1', data2: 'data 2' }; const fn1 = (data, cb) => { process.nextTick(() => { cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { process.nextTick(() => { cb(null, { data2: 'data 2' }); }); }; const fc1 = metasync([[fn1, fn2]]); const fc2 = fc1.clone(); fc1(data, (err, data) => { test.error(err); test.strictSame(data, expectedData); fc2(data, (err, data) => { test.error(err); test.strictSame(data, expectedData); test.end(); }); }); }); ================================================ FILE: test/compose.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('async parallel functions composition', (test) => { const data = { test: 'data' }; const expectedData = { test: 'data', data1: 'data 1', data2: 'data 2' }; const fn1 = (data, cb) => { process.nextTick(() => { cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { process.nextTick(() => { cb(null, { data2: 'data 2' }); }); }; const fc = metasync([[fn1, fn2]]); fc(data, (err, data) => { test.error(err); test.strictSame(data, expectedData); test.end(); }); }); metatests.test('async complex functions composition', (test) => { const data = { test: 'data' }; const expectedDataInFn1 = { test: 'data' }; const expectedDataInFn2 = { test: 'data', data1: 'data 1' }; const expectedDataInRes = { test: 'data' }; let i; for (i = 1; i < 6; i++) { expectedDataInRes['data' + i] = 'data ' + i; } const fn1 = (data, cb) => { process.nextTick(() => { test.strictSame(data, expectedDataInFn1); cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { process.nextTick(() => { test.strictSame(data, expectedDataInFn2); cb(null, { data2: 'data 2' }); }); }; const fn3 = (data, cb) => { process.nextTick(() => { cb(null, { data3: 'data 3' }); }); }; const fn4 = (data, cb) => { process.nextTick(() => { cb(null, { data4: 'data 4' }); }); }; const fn5 = (data, cb) => { process.nextTick(() => { test.strictSame(data.data1, 'data 1'); test.strictSame(data.data2, 'data 2'); test.strictSame(data.data3, 'data 3'); test.strictSame(data.data4, 'data 4'); cb(null, { data5: 'data 5' }); }); }; const fc = metasync([fn1, fn2, [[fn3, [fn4, fn5]]], [], [[]]]); fc(data, (err, data) => { test.error(err); test.strictSame(data, expectedDataInRes); test.end(); }); }); const AC1 = 'async functions composition cancel before start'; metatests.test(AC1, (test) => { let count = 0; const fn1 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 1')); }; const fn2 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 2')); }; const fn3 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 3')); }; const fn4 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 4')); }; const fc = metasync([fn1, [[fn2, fn3]], fn4]); fc.cancel(); fc({}, (err, data) => { test.isError(err); test.strictSame(data, undefined); test.strictSame(count, 0); test.end(); }); }); const AC2 = 'async functions composition cancel in the middle'; metatests.test(AC2, (test) => { let count = 0; let finished = 0; const fn1 = (data, cb) => { count++; process.nextTick(() => { finished++; cb(null, 'data 1'); }); }; const fn2 = (data, cb) => { count++; setTimeout(() => { finished++; cb(null, 'data 2'); }, 200); }; const fn3 = (data, cb) => { count++; setTimeout(() => { finished++; cb(null, 'data 3'); }, 200); }; const fn4 = (data, cb) => { count++; setTimeout(() => { finished++; cb(null, 'data 4'); }, 200); }; const fc = metasync([fn1, [[fn2, fn3]], fn4]); fc({}, (err, data) => { test.isError(err); test.strictSame(data, undefined); test.strictSame(count, 3); test.strictSame(finished, 1); test.end(); }); setTimeout(() => { fc.cancel(); }, 100); }); metatests.test('async functions composition cancel after end', (test) => { let count = 0; const fn1 = (data, cb) => { count++; process.nextTick(() => { cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { count++; process.nextTick(() => { cb(null, { data2: 'data 2' }); }); }; const fn3 = (data, cb) => { count++; process.nextTick(() => { cb(null, { data3: 'data 3' }); }); }; const fn4 = (data, cb) => { count++; process.nextTick(() => { cb(null, { data4: 'data 4' }); }); }; const fc = metasync([fn1, [[fn2, fn3]], fn4]); fc({}, (err, data) => { test.error(err); test.strictSame(data, { data1: 'data 1', data2: 'data 2', data3: 'data 3', data4: 'data 4', }); test.strictSame(count, 4); test.end(); }); setTimeout(() => { fc.cancel(); }, 100); }); metatests.test('async functions composition to array', (test) => { let count = 0; const fn1 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 1')); }; const fn2 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 2')); }; const fn3 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 3')); }; const fn4 = (data, cb) => { count++; process.nextTick(() => cb(null, 'data 4')); }; const fc = metasync([fn1, [[fn2, fn3]], fn4]); fc(['data 0'], (err, data) => { test.error(err); test.strictSame(data, ['data 0', 'data 1', 'data 2', 'data 3', 'data 4']); test.strictSame(count, 4); test.end(); }); }); ================================================ FILE: test/compose.then.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('successful then', (test) => { let finishedFuncsCount = 0; const res1 = 'res1'; const af1 = (data, callback) => process.nextTick(() => { test.strictSame(data, {}); test.strictSame(finishedFuncsCount, 0); finishedFuncsCount++; callback(null, { res1 }); }); const res2 = 'res2'; const af2 = (data, callback) => process.nextTick(() => { test.strictSame(data, { res1 }); test.strictSame(finishedFuncsCount, 1); finishedFuncsCount++; callback(null, { res2 }); }); const res3 = 'res3'; const af3 = (data, callback) => process.nextTick(() => { const keysCount = Object.keys(data).length; test.ok(keysCount >= 2 && keysCount < 4); test.ok(finishedFuncsCount >= 2 && finishedFuncsCount < 4); finishedFuncsCount++; callback(null, { res3 }); }); const res4 = 'res4'; const af4 = (data, callback) => process.nextTick(() => { const keysCount = Object.keys(data).length; test.ok(keysCount >= 2 && keysCount < 4); test.ok(finishedFuncsCount >= 2 && finishedFuncsCount < 4); finishedFuncsCount++; callback(null, { res4 }); }); const faf1 = metasync([af1, [[af2, af3]], af4]); faf1().then( (res) => { test.strictSame(res, { res1: 'res1', res2: 'res2', res3: 'res3', res4: 'res4', }); test.strictSame(finishedFuncsCount, 4); test.end(); }, (err) => { test.error(err); }, ); }); ================================================ FILE: test/composition.cancel.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); // Emulate Asynchronous calls of function // callback - function const wrapAsync = (callback) => { setTimeout(callback, Math.floor(Math.random() * 500)); }; metatests.test('async functions composition cancel in the middle', (test) => { let fc = null; const fn1 = test.mustCall((data, cb) => { wrapAsync(() => { cb(null, 1); }); }); const fn2 = test.mustCall((data, cb) => { test.strictSame(data, [1]); wrapAsync(() => { if (fc) fc.cancel(); cb(null, 2); }); }); const fn3 = test.mustNotCall(); const fn4 = test.mustNotCall(); fc = metasync([fn1, fn2, fn3, fn4]); fc([], (err, data) => { test.isError(err, new Error('Metasync: asynchronous composition canceled')); test.strictSame(data, undefined); test.end(); }); }); ================================================ FILE: test/composition.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const fs = require('fs'); const getPerson = (context, cb) => { const persons = [ { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, ]; const person = persons.find((p) => p.name === context.name); cb(null, { person }); }; const lookupCountry = (context, cb) => { const dictionary = { Rome: 'Roman Empire', Shaoshan: 'Quin Empire', }; const country = dictionary[context.person.city]; cb(null, { country }); }; const prepareResult = (context, cb) => { const result = Object.assign({}, context.person, { country: context.country, length: context.buffer.length, }); cb(null, { result }); }; metatests.test('sync complex functions composition', (test) => { const readFile = (context, cb) => { fs.readFile(context.file, (err, buffer) => { test.error(err); cb(null, { buffer }); }); }; const fc = metasync([getPerson, [[lookupCountry, readFile]], prepareResult]); fc( { name: 'Mao Zedong', file: './AUTHORS', }, (err, context) => { test.error(err); const expected = { name: 'Mao Zedong', city: 'Shaoshan', born: 1893, country: 'Quin Empire', length: 318, }; test.strictSame(context.result, expected); test.end(); }, ); }); ================================================ FILE: test/composition.parallel.js ================================================ 'use strict'; const metatests = require('metatests'); const metasync = require('..'); // Emulate Asynchronous calls of function // callback - function const wrapAsync = (callback) => { setTimeout(callback, Math.floor(Math.random() * 500)); }; metatests.test('async complex functions composition', (test) => { const fn1 = test.mustCall((data, cb) => { wrapAsync(() => { cb(null, 1); }); }); const fn2 = test.mustCall((data, cb) => { wrapAsync(() => { cb(null, 2); }); }); const fn3 = test.mustCall((data, cb) => { test.strictSame(data.length, 2); wrapAsync(() => { cb(null, 3); }); }); const fn4 = test.mustCall((data, cb) => { wrapAsync(() => { cb(null, 4); }); }); const fc = metasync([[[fn1, fn2]], fn3, fn4]); fc([], (err, data) => { test.error(err); test.strictSame(data.length, 4); test.end(); }); }); ================================================ FILE: test/composition.pause.js ================================================ 'use strict'; const metatests = require('metatests'); const metasync = require('..'); const fs = require('fs'); const getPerson = (context, cb) => { const persons = [ { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, ]; const person = persons.find((p) => p.name === context.name); cb(null, { person }); }; const lookupCountry = (context, cb) => { const dictionary = { Rome: 'Roman Empire', Shaoshan: 'Quin Empire', }; const country = dictionary[context.person.city]; cb(null, { country }); }; const prepareResult = (context, cb) => { const result = Object.assign({}, context.person, { country: context.country, length: context.buffer.length, }); cb(null, { result }); }; metatests.test('async functions composition pause in the middle', (test) => { const readFile = (context, cb) => { fs.readFile(context.file, (err, buffer) => { test.error(err); cb(null, { buffer }); }); }; const fc = metasync([getPerson, [[lookupCountry, readFile]], prepareResult]); fc( { name: 'Mao Zedong', file: './AUTHORS', }, (err, context) => { test.error(err); const expected = { name: 'Mao Zedong', city: 'Shaoshan', born: 1893, country: 'Quin Empire', length: 318, }; test.strictSame(context.result, expected); test.end(); }, ); process.nextTick(() => { fc.pause(); setTimeout(() => { fc.resume(); }, 1000); }); }); ================================================ FILE: test/composition.sequential.js ================================================ 'use strict'; const metatests = require('metatests'); const metasync = require('..'); metatests.test('sequential functions', (test) => { const f1 = test.mustCall((c, cb) => cb()); const f2 = test.mustCall((c, cb) => cb()); const f3 = test.mustCall((c, cb) => cb()); const f4 = test.mustCall((c, cb) => cb()); const fc = metasync([f1, f2, f3, f4]); fc((err, context) => { test.error(err); test.strictSame(context, {}); test.end(); }); }); ================================================ FILE: test/control.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('firstOf', (test) => { const returningFnIndex = 2; let dataReturned = false; const execUnlessDataReturned = (data) => (callback) => { if (dataReturned) { callback(null, data); } else { process.nextTick(execUnlessDataReturned); } }; const makeIFn = (i) => (callback) => process.nextTick(() => { const iData = 'data' + i; if (i === returningFnIndex) { dataReturned = true; callback(null, iData); } else { execUnlessDataReturned(iData); } }); const fns = [1, 2, 3].map(makeIFn); metasync.firstOf(fns, (err, data) => { test.error(err); test.strictSame(data, 'data2'); test.end(); }); }); metatests.test('parallel with error', (test) => { const parallelError = new Error('Parallel error'); const fn1 = (data, cb) => { process.nextTick(() => { cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { process.nextTick(() => { cb(parallelError); }); }; metasync.parallel([fn1, fn2], (err, res) => { test.strictSame(err, parallelError); test.strictSame(res, undefined); test.end(); }); }); metatests.test('sequential with error', (test) => { const sequentialError = new Error('Sequential error'); const expectedDataInFn2 = { data1: 'data 1' }; const fn1 = (data, cb) => { process.nextTick(() => { cb(null, { data1: 'data 1' }); }); }; const fn2 = (data, cb) => { process.nextTick(() => { test.same(data, expectedDataInFn2); cb(sequentialError); }); }; metasync.sequential([fn1, fn2], (err, res) => { test.strictSame(err, sequentialError); test.strictSame(res, undefined); test.end(); }); }); metatests.test('runIf run asyncFn', (test) => { const asyncFn = test.mustCall((arg1, arg2, cb) => { process.nextTick(() => { cb(null, { arg1, arg2 }); }); }); metasync.runIf(true, asyncFn, 'val1', 'val2', (err, result) => { test.error(err); test.strictSame(result, { arg1: 'val1', arg2: 'val2' }); test.end(); }); }); metatests.test('runIf do not run asyncFn', (test) => { const asyncFn = test.mustNotCall((arg1, arg2, cb) => { process.nextTick(() => { cb(null, { arg1, arg2 }); }); }); metasync.runIf(false, asyncFn, 'val1', 'val2', (err, result) => { test.error(err); test.strictSame(result, undefined); test.end(); }); }); metatests.test('runIf default value', (test) => { const asyncFn = test.mustNotCall((val, cb) => { process.nextTick(() => { cb(null, val); }); }); metasync.runIf(false, 'default', asyncFn, 'val', (err, result) => { test.error(err); test.strictSame(result, 'default'); test.end(); }); }); metatests.test('runIf forward an error', (test) => { const asyncFn = test.mustCall((cb) => { process.nextTick(() => { cb(new Error()); }); }); metasync.runIf(true, asyncFn, (err) => { test.isError(err); test.end(); }); }); metatests.test('runIfFn', (test) => { test.plan(5); const value = 42; const asyncFn = (cb) => { cb(null, value); }; metasync.runIfFn(test.mustCall(asyncFn), (err, res) => { test.error(err); test.strictSame(res, value); }); metasync.runIfFn(null, (err, res) => { test.error(err); test.assertNot(res); }); }); ================================================ FILE: test/do.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); // Emulate Asynchronous calls of function // callback - const wrapAsync = (callback) => { setTimeout(callback, Math.floor(Math.random() * 500)); }; metatests.test('simple chain/do', (test) => { const readConfig = test.mustCall((name, callback) => { test.strictSame(name, 'myConfig'); wrapAsync(() => { callback(null, { name }); }); }); const selectFromDb = test.mustCall((query, callback) => { test.strictSame(query, 'select * from cities'); wrapAsync(() => { callback(null, [{ name: 'Kiev' }, { name: 'Roma' }]); }); }); const getHttpPage = test.mustCall((url, callback) => { test.strictSame(url, 'http://kpi.ua'); wrapAsync(() => { callback(null, 'Some archaic web here'); }); }); const readFile = test.mustCall((path, callback) => { test.strictSame(path, 'README.md'); wrapAsync(() => { callback(null, 'file content'); }); }); const c1 = metasync .do(readConfig, 'myConfig') .do(selectFromDb, 'select * from cities') .do(getHttpPage, 'http://kpi.ua') .do(readFile, 'README.md'); c1((err, result) => { test.error(err); test.strictSame(result, 'file content'); test.end(); }); }); ================================================ FILE: test/examples.js ================================================ 'use strict'; const metasync = require('..'); const fs = require('fs'); const metatests = require('metatests'); const path = require('path'); const ASYNC_TIMEOUT = 200; // Data Collector metatests.test('dataCollector / simple', (test) => { const dataCollector = metasync.collect(4).done((err, data) => { test.error(err); test.strictSame(Object.keys(data).length, 4); test.end(); }); dataCollector.collect('user', null, { name: 'Marcus Aurelius' }); fs.readFile(path.join(__dirname, '..', 'README.md'), (err, data) => { dataCollector.collect('readme', err, data); }); fs.readFile(path.join(__dirname, '..', 'AUTHORS'), (err, data) => { dataCollector.collect('authors', err, data); }); setTimeout(() => { dataCollector.pick('timer', { date: new Date() }); }, ASYNC_TIMEOUT); }); metatests.test('data collector / timeout', (test) => { const expectedErr = new Error('Metasync: Collector timed out'); const expectedData = { user: { name: 'Marcus Aurelius' } }; const dataCollector = metasync .collect(4) .timeout(1000) .done((err, data) => { test.isError(err, expectedErr); test.strictSame(data, expectedData); test.end(); }); dataCollector.pick('user', { name: 'Marcus Aurelius' }); }); metatests.test('data collector / error', (test) => { const expectedErr = new Error('User not found'); const expectedData = { file: 'file content' }; const dataCollector = metasync.collect(4).done((err, data) => { test.isError(err, expectedErr); test.strictSame(data, expectedData); test.end(); }); dataCollector.pick('file', 'file content'); dataCollector.fail('user', new Error('User not found')); dataCollector.pick('score', 1000); dataCollector.fail('tcp', new Error('No socket')); }); // Key Collector metatests.test('key collector / simple', (test) => { const keyCollector = metasync .collect(['user', 'readme']) .timeout(2000) .done((err, data) => { test.error(err); test.strictSame(Object.keys(data).length, 2); test.end(); }); keyCollector.pick('user', { name: 'Marcus Aurelius' }); fs.readFile(path.join(__dirname, '..', 'README.md'), (err, data) => { test.error(err); keyCollector.pick('readme', data); }); }); // Parallel execution metatests.test('parallel', (test) => { test.plan(5); const expectedData = { data1: 'result1', data2: 'result2', data3: 'result3', arg: 'arg', }; const pf1 = (data, callback) => { test.pass('must call'); setTimeout(() => callback(null, { data1: 'result1' }), ASYNC_TIMEOUT); }; const pf2 = (data, callback) => { test.pass('must call'); setTimeout(() => callback(null, { data2: 'result2' }), ASYNC_TIMEOUT); }; const pf3 = (data, callback) => { test.pass('must call'); setTimeout(() => callback(null, { data3: 'result3' }), ASYNC_TIMEOUT); }; metasync.parallel([pf1, pf2, pf3], { arg: 'arg' }, (err, data) => { test.error(err); test.strictSame(data, expectedData); }); }); // Sequential execution metatests.test('sequential', (test) => { test.plan(5); const sf1 = (data, callback) => { test.strictSame(data, ['arg']); setTimeout(() => callback(null, 'result1'), ASYNC_TIMEOUT); }; const sf2 = (data, callback) => { test.pass('must call'); setTimeout(() => callback(null, 'result2'), ASYNC_TIMEOUT); }; const sf3 = (data, callback) => { test.strictSame(data, ['arg', 'result1', 'result2']); setTimeout(() => callback(null, 'result3'), ASYNC_TIMEOUT); }; metasync.sequential([sf1, sf2, sf3], ['arg'], (err, data) => { test.error(err); test.strictSame(data, ['arg', 'result1', 'result2', 'result3']); }); }); // Asynchrous filter metatests.test('asynchronus filter', (test) => { const dataToFilter = [ 'Lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua', ]; const expectedResult = [ 'Lorem', 'ipsum', 'sit', 'amet', 'elit', 'sed', 'do', 'eiusmod', 'tempor', 'ut', 'labore', 'et', ]; const filterPredicate = (item, callback) => { // filter words which consists of unique letters only const letters = []; for (let i = 0; i < item.length; ++i) { if (letters.includes(item[i].toLowerCase())) break; letters.push(item[i].toLowerCase()); } setTimeout( () => callback(null, letters.length === item.length), ASYNC_TIMEOUT, ); }; metasync.filter(dataToFilter, filterPredicate, (err, result) => { test.error(err); test.strictSame(result, expectedResult); test.end(); }); }); // Asynchrous find metatests.test('asynchronus find', (test) => { metasync.find( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], (item, callback) => callback(null, item % 3 === 0 && item % 5 === 0), (err, result) => { test.error(err); test.strictSame(result, 15); test.end(); }, ); }); // Asynchrous some metatests.test('asynchronus some', (test) => { metasync.some( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], (item, callback) => { const res = item % 3 === 0 && item % 5 === 0; callback(null, res); }, (err, result) => { test.error(err); test.strictSame(result, true); test.end(); }, ); }); // Asyncronous each in parallel metatests.test('asyncronous each', (test) => { const result = {}; metasync.each( ['a', 'b', 'c'], (item, callback) => { result[item] = item; callback(null); }, (err) => { test.error(err); test.strictSame(result, { a: 'a', b: 'b', c: 'c' }); test.end(); }, ); }); // Asyncronous series (sequential) metatests.test('asynchronus series', (test) => { const result = []; metasync.series( ['a', 'b', 'c', 'd'], (item, callback) => { result.push(item.toUpperCase()); callback(null); }, (err) => { test.error(err); test.strictSame(result, ['A', 'B', 'C', 'D']); test.end(); }, ); }); // Asyncronous reduce (sequential) metatests.test('asynchronus reduce', (test) => { metasync.reduce( ['a', 'b', 'c', 'd'], (prev, curr, callback) => { callback(null, prev + curr); }, (err, data) => { test.error(err); test.strictSame(data, 'abcd'); test.end(); }, ); }); // Queue metatests.test('queue / simple', (test) => { const expectedResult = [1, 2, 3, 4, 5, 6, 8, 9]; const result = []; const queue = metasync .queue(3) .process((item, callback) => { result.push(item.id); setTimeout(callback, 100); }) .drain(() => { test.strictSame(result, expectedResult); test.end(); }); queue.add({ id: 1 }); queue.add({ id: 2 }); queue.add({ id: 3 }); queue.add({ id: 4 }); queue.add({ id: 5 }); queue.add({ id: 6 }); queue.add({ id: 8 }); queue.add({ id: 9 }); }); metatests.test('queue / pause', (test) => { const expectedResult = [1, 3, 4, 7, 8, 9]; const result = []; const queue = metasync .queue(3) .process((item, callback) => { result.push(item.id); setTimeout(callback, 100); }) .drain(() => { test.strictSame(result, expectedResult); test.end(); }); queue.add({ id: 1 }); queue.pause(); queue.add({ id: 2 }); queue.resume(); queue.add({ id: 3 }); queue.add({ id: 4 }); queue.add({ id: 5 }); queue.add({ id: 6 }); queue.clear(); queue.add({ id: 7 }); queue.add({ id: 8 }); queue.add({ id: 9 }); }); // Trottle metatests.test('trottle', (test) => { const expectedResult = ['A', 'E', 'F', 'I']; const result = []; let state; const fn = () => { result.push(state); }; const f1 = metasync.throttle(500, fn); // to be called 2 times (first and last: A and E) state = 'A'; f1(); state = 'B'; f1(); state = 'C'; f1(); state = 'D'; f1(); state = 'E'; f1(); // to be called 2 times (last will be I) setTimeout(() => { state = 'F'; f1(); }, 600); setTimeout(() => { state = 'G'; f1(); }, 700); setTimeout(() => { state = 'H'; f1(); }, 1000); setTimeout(() => { state = 'I'; f1(); }, 1100); setTimeout(() => { test.strictSame(result, expectedResult); test.end(); }, 3000); }); // Debounce metatests.test('debounce', (test) => { const expectedResult = ['E', 'I']; const result = []; let state; const fn = () => { result.push(state); }; const f1 = metasync.debounce(500, fn, ['I']); // to be called one time (E) state = 'A'; f1(); state = 'B'; f1(); state = 'C'; f1(); state = 'D'; f1(); state = 'E'; f1(); // to be called one time (I) setTimeout(() => { state = 'F'; f1(); }, 600); setTimeout(() => { state = 'G'; f1(); }, 700); setTimeout(() => { state = 'H'; f1(); }, 1000); setTimeout(() => { state = 'I'; f1(); }, 1100); setTimeout(() => { test.strictSame(result, expectedResult); test.end(); }, 3000); }); // Map metatests.test('asynchronus map / simple', (test) => { metasync.map( [1, 2, 3], (item, callback) => { setTimeout(() => { callback(null, item * item); }, item * 10); }, (error, result) => { test.error(error); test.strictSame(result, [1, 4, 9]); test.end(); }, ); }); metatests.test('asynchronus map / error', (test) => { metasync.map( [1, 2, 3], (item, callback) => { setTimeout(() => { if (item === 2) { callback(new Error()); } else { callback(null, item); } }, item * 10); }, (error, result) => { test.isError(error); test.strictSame(result, undefined); test.end(); }, ); }); // Timeout metatests.test('timeout', (test) => { metasync.timeout( 200, (done) => { setTimeout(done, 300); }, (err) => { const expectedErr = new Error( 'Metasync: asynchronous function timed out', ); test.isError(err, expectedErr); test.end(); }, ); }); ================================================ FILE: test/firstOf.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('firstOf', (test) => { const returningFnIndex = 2; let dataReturned = false; const execUnlessDataReturned = (data) => (callback) => { if (dataReturned) { callback(null, data); } else { process.nextTick(execUnlessDataReturned); } }; const makeIFn = (i) => (callback) => process.nextTick(() => { const iData = 'data' + i; if (i === returningFnIndex) { dataReturned = true; callback(null, iData); } else { execUnlessDataReturned(iData); } }); const fns = [1, 2, 3].map(makeIFn); metasync.firstOf(fns, (err, data) => { test.error(err); test.strictSame(data, 'data2'); test.end(); }); }); ================================================ FILE: test/fp.ap.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const asyncArgs = (callback) => process.nextTick(() => callback(null, 4, 5)); const functionInCallback = (callback) => process.nextTick(() => callback(null, (x, y) => x + y)); const asyncError = new Error('Async error'); const asyncErrorCb = (callback) => process.nextTick(() => callback(asyncError)); metatests.test('two successful functions', (test) => { metasync.ap( asyncArgs, functionInCallback, )((err, res) => { test.error(err); test.strictSame(res, 9); test.end(); }); }); metatests.test('first function with error', (test) => { metasync.ap( asyncErrorCb, functionInCallback, )((err, res) => { test.strictSame(err, asyncError); test.strictSame(res, undefined); test.end(); }); }); metatests.test('second function with error', (test) => { metasync.ap( asyncArgs, asyncErrorCb, )((err, res) => { test.strictSame(err, asyncError); test.strictSame(res, undefined); test.end(); }); }); ================================================ FILE: test/fp.asAsync.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const asyncSum = (x, y, callback) => process.nextTick(() => callback(null, x + y)); const tripleFnInCb = (callback) => process.nextTick(() => callback(null, (x) => x * 3)); const asyncMultBy11 = (x, callback) => process.nextTick(() => callback(null, x * 11)); metatests.test('asAsync all functions test', (test) => { const done = (err, res) => { test.error(err); test.strictSame(res, 1848); test.end(); }; metasync .asAsync(asyncSum, 3, 5) .fmap((x) => x * 7) .ap(tripleFnInCb) .concat(asyncMultBy11)(done); }); ================================================ FILE: test/fp.concat.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const asyncData = 'data'; const asyncDataCb = (callback) => process.nextTick(() => callback(null, asyncData)); const asyncTwice = (str, callback) => process.nextTick(() => callback(null, str + str)); const asyncError = new Error('Async error'); const asyncErrorCb = (callback) => process.nextTick(() => callback(asyncError)); const asyncTransformErrorCb = (str, callback) => process.nextTick(() => callback(asyncError)); metatests.test('two successful functions', (test) => { metasync.concat( asyncDataCb, asyncTwice, )((err, res) => { test.error(err); test.strictSame(res, 'datadata'); test.end(); }); }); metatests.test('first function error', (test) => { metasync.concat( asyncErrorCb, asyncTwice, )((err, res) => { test.strictSame(err, asyncError); test.strictSame(res, undefined); test.end(); }); }); metatests.test('second function error', (test) => { metasync.concat( asyncDataCb, asyncTransformErrorCb, )((err, res) => { test.strictSame(err, asyncError); test.strictSame(res, undefined); test.end(); }); }); ================================================ FILE: test/fp.fmap.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); const asyncData = 'data'; const asyncDataCb = (callback) => process.nextTick(() => { callback(null, asyncData); }); const asyncError = new Error('Async error'); const asyncErrorCb = (callback) => process.nextTick(() => { callback(asyncError); }); const identity = (x) => x; const repeatStringTwice = (str) => str + str; const appendColon = (str) => str + ':'; const twiceAndColon = (str) => appendColon(repeatStringTwice(str)); metatests.test('Result transformation', (test) => { const expected = 'data:'; metasync.fmap( asyncDataCb, appendColon, )((err, res) => { test.error(err); test.strictSame(expected, res); test.end(); }); }); metatests.test('Getting asynchronous error', (test) => { metasync.fmap( asyncErrorCb, appendColon, )((err, res) => { test.strictSame(err, asyncError); test.strictSame(res, undefined); test.end(); }); }); const FP1 = 'Getting error with no second argument execution'; metatests.test(FP1, (test) => { let executed = false; metasync.fmap(asyncErrorCb, (str) => { executed = true; return appendColon(str); })(() => { test.strictSame(executed, false); test.end(); }); }); metatests.test('functor law I', (test) => { metasync.fmap( asyncDataCb, identity, )((err, res) => { test.error(err); test.strictSame(asyncData, res); test.end(); }); }); metatests.test('functor law II', (test) => { const fmap = metasync.fmap; const asyncTwice = fmap(asyncDataCb, repeatStringTwice); const asyncTwiceAndColon = fmap(asyncTwice, appendColon); asyncTwiceAndColon((err1, res1) => { fmap( asyncDataCb, twiceAndColon, )((err2, res2) => { test.error(err1); test.error(err2); test.strictSame(res1, res2); test.end(); }); }); }); ================================================ FILE: test/fp.of.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('of test', (test) => { const args = [1, 2, 3, 4, 5]; metasync.of(...args)((err, ...argsCb) => { test.error(err); test.strictSame(args, argsCb); test.end(); }); }); ================================================ FILE: test/memoize.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('memoize', (test) => { const storage = { file1: Buffer.from('file1'), file2: Buffer.from('file2'), }; const getData = (file, callback) => { process.nextTick(() => { const result = storage[file]; if (result) callback(null, result); else callback(new Error('File not found')); }); }; const memoizedGetData = metasync.memoize(getData); const keys = []; memoizedGetData.on('memoize', (key) => { keys.push(key); }); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, storage.file1); memoizedGetData('file2', (err, data) => { test.error(err); test.strictSame(data, storage.file2); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, storage.file1); memoizedGetData('file2', (err, data) => { test.error(err); test.strictSame(data, storage.file2); test.strictSame(keys, ['file1', 'file2']); test.end(); }); }); }); }); }); metatests.test('memoize clear cache', (test) => { const storage = { file1: Buffer.from('file1'), }; const getData = (file, callback) => { process.nextTick(() => { const result = storage[file]; if (result) callback(null, result); else callback(new Error('File not found')); }); }; const memoizedGetData = metasync.memoize(getData); let onClear = false; memoizedGetData.on('clear', () => { onClear = true; }); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, storage.file1); storage.file1 = Buffer.from('changed'); memoizedGetData.clear(); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, Buffer.from('changed')); test.strictSame(onClear, true); test.end(); }); }); }); metatests.test('memoize cache del', (test) => { const storage = { file1: Buffer.from('file1'), }; const getData = (file, callback) => { process.nextTick(() => { const result = storage[file]; if (result) callback(null, result); else callback(new Error('File not found')); }); }; const memoizedGetData = metasync.memoize(getData); let onDel = false; memoizedGetData.on('del', () => { onDel = true; }); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, storage.file1); storage.file1 = Buffer.from('changed'); memoizedGetData.del('file1'); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, Buffer.from('changed')); test.strictSame(onDel, true); test.end(); }); }); }); metatests.test('memoize cache add', (test) => { const getData = (file, callback) => { process.nextTick(() => { const result = Buffer.from('added'); callback(null, result); }); }; const memoizedGetData = metasync.memoize(getData); let onAdd = false; memoizedGetData.on('add', () => { onAdd = true; }); const file1 = Buffer.from('added'); memoizedGetData.add('file1', null, file1); memoizedGetData('file1', (err, data) => { test.error(err); test.strictSame(data, Buffer.from('added')); test.strictSame(onAdd, true); test.end(); }); }); metatests.test('memoize cache get', (test) => { const getData = (file, callback) => { process.nextTick(() => { const result = Buffer.from('added'); callback(null, result); }); }; const memoizedGetData = metasync.memoize(getData); const file1 = Buffer.from('added'); memoizedGetData.add('file1', null, file1); memoizedGetData.get('file1', (err, data) => { test.error(err); test.strictSame(data, Buffer.from('added')); test.end(); }); }); ================================================ FILE: test/poolify.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('poolify simple', (test) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 10, 15, 20); pool((item1) => { test.strictSame(pool.items.length, 14); test.strictSame(item1.length, 128); pool((item2) => { test.strictSame(pool.items.length, 13); test.strictSame(item2.length, 128); pool([item1]); pool([item2]); test.strictSame(pool.items.length, 15); test.end(); }); }); }); metatests.test('poolify loop', (test) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 10, 15, 20); for (let i = 0; i < 15; i++) { pool((item) => { pool([item]); if (i === 14) { test.strictSame(item.length, 128); test.end(); } }); } }); metatests.test('poolify max', (test) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 5, 7, 10); for (let i = 0; i < 15; i++) { pool((item) => { setTimeout(() => { pool([item]); if (i === 14) { test.end(); } }, 100); }); } }); metatests.test('poolify delayed order', (test) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 0, 2, 2); let get3 = false; pool((item1) => { test.strictSame(pool.items.length, 1); pool((item2) => { test.strictSame(pool.items.length, 0); pool((item3) => { test.strictSame(pool.items.length, 0); test.strictSame(get3, false); get3 = true; pool([item3]); }); pool((item4) => { test.strictSame(pool.items.length, 1); test.strictSame(get3, true); pool([item4]); test.end(); }); pool([item1]); pool([item2]); }); }); }); metatests.test('poolify functor', (test) => { const adder = (a) => (b) => adder(a + b); const pool = metasync.poolify(adder, 1, 2, 3); pool((item1) => { test.strictSame(pool.items.length, 1); pool((item2) => { test.strictSame(pool.items.length, 0); pool([item1]); pool([item2]); test.strictSame(pool.items.length, 2); test.end(); }); }); }); metatests.test('poolify get sync', (test) => { const adder = (a) => (b) => adder(a + b); const pool = metasync.poolify(adder, 1, 2, 3); const item1 = pool(); test.strictSame(pool.items.length, 1); const item2 = pool(); test.strictSame(pool.items.length, 0); const item3 = pool(); test.strictSame(pool.items.length, 0); const item4 = pool(); test.strictSame(item4, undefined); test.strictSame(pool.items.length, 0); pool([item1]); pool([item2]); pool([item3]); test.strictSame(pool.items.length, 3); test.end(); }); ================================================ FILE: test/queue.both.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('priority / roundRobin', (test) => { const expectedResult = [ 1, 2, 3, 8, 17, 13, 7, 4, 21, 12, 16, 11, 20, 15, 19, 10, 23, 14, 18, 9, 22, 6, 5, ]; const result = []; const q = metasync .queue(3) .roundRobin() .priority() .process((item, cb) => { result.push(item.id); setTimeout(cb, 100); }); q.drain(() => { test.strictSame(result, expectedResult); test.end(); }); q.add({ id: 1 }, 1, 10); q.add({ id: 2 }, 2, 10); q.add({ id: 3 }, 3, 10); q.add({ id: 4 }, 4, 20); q.add({ id: 5 }, 1, 10); q.add({ id: 6 }, 2, 10); q.add({ id: 7 }, 3, 10); q.add({ id: 8 }, 4, 50); q.add({ id: 9 }, 2, 50); q.add({ id: 10 }, 2, 60); q.add({ id: 11 }, 2, 70); q.add({ id: 12 }, 2, 80); q.add({ id: 13 }, 2, 90); q.add({ id: 14 }, 2, 60); q.add({ id: 15 }, 2, 70); q.add({ id: 16 }, 1, 80); q.add({ id: 17 }, 1, 90); q.add({ id: 18 }, 1, 60); q.add({ id: 19 }, 1, 70); q.add({ id: 20 }, 1, 80); q.add({ id: 21 }, 1, 90); q.add({ id: 22 }, 1, 60); q.add({ id: 23 }, 1, 70); }); ================================================ FILE: test/queue.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('queue add', (test) => { const queue = metasync.queue(3).timeout(2000); let taskIndex = 1; queue.process((item, callback) => { process.nextTick(() => { test.strictSame(item, { id: taskIndex }); taskIndex++; callback(null); }); }); queue.drain(() => { test.end(); }); let id; for (id = 1; id < 10; id++) { queue.add({ id }); } }); metatests.test('queue pause resume clear', (test) => { const queue = metasync.queue(3); queue.pause(); queue.add({ id: 1 }); test.strictSame(queue.count, 0); let itemIsProcessed = false; queue.process((item, callback) => { itemIsProcessed = true; callback(null); }); queue.add({ id: 2 }); test.strictSame(itemIsProcessed, false); queue.resume(); test.strictSame(queue.paused, false); queue.clear(); test.strictSame(queue.count, 0); test.end(); }); metatests.test('queue with no process function and no timeout', (test) => { const queue = metasync.queue(3); queue.add({ id: 1 }); queue.add({ id: 2 }); queue.add({ id: 3 }); test.strictSame(queue.count, 0); test.end(); }); metatests.test('queue with timeout event', (test) => { const timeoutErr = new Error('Metasync: Queue timed out'); const queue = metasync.queue(3); queue.process((item, callback) => { setTimeout(() => { callback(null, item); }, 1000); }); queue.timeout(1, (err, res) => { test.strictSame(err, timeoutErr); test.strictSame(res, undefined); test.end(); }); queue.add({ id: 1 }); }); ================================================ FILE: test/queue.lifo.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('lifo / simple', (test) => { const expectedResult = [1, 2, 3, 9, 8, 7, 6, 5, 4]; const result = []; const q = metasync .queue(3) .priority() .lifo() .process((item, cb) => { result.push(item.id); setTimeout(cb, 100); }); q.drain(() => { test.strictSame(result, expectedResult); test.end(); }); q.add({ id: 1 }); q.add({ id: 2 }); q.add({ id: 3 }); q.add({ id: 4 }); q.add({ id: 5 }); q.add({ id: 6 }); q.add({ id: 7 }); q.add({ id: 8 }); q.add({ id: 9 }); }); ================================================ FILE: test/queue.modes.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('queue default FIFO', (test) => { const queue = metasync.queue(3).timeout(1); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 4, 5, 6, 7, 8, 9]); test.end(); }); for (let id = 1; id < 10; id++) { queue.add({ id }); } }); metatests.test('queue FIFO', (test) => { const queue = metasync.queue(3).fifo().timeout(1); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 4, 5, 6, 7, 8, 9]); test.end(); }); for (let id = 1; id < 10; id++) { queue.add({ id }); } }); metatests.test('queue LIFO', (test) => { const queue = metasync.queue(3).lifo().timeout(1); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 9, 8, 7, 6, 5, 4]); test.end(); }); for (let id = 1; id < 10; id++) { queue.add({ id }); } }); metatests.test('queue priority', (test) => { const queue = metasync.queue(3).priority(); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 8, 5, 4, 7, 9, 6]); test.end(); }); queue.add({ id: 1 }, 1); queue.add({ id: 2 }, 4); queue.add({ id: 3 }, 5); queue.add({ id: 4 }, 7); queue.add({ id: 5 }, 8); queue.add({ id: 6 }, 2); queue.add({ id: 7 }, 6); queue.add({ id: 8 }, 9); queue.add({ id: 9 }, 3); }); metatests.test('queue round robin', (test) => { const queue = metasync.queue(3).roundRobin(); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 4, 7, 5, 8, 6, 9]); test.end(); }); queue.add({ id: 1 }, 5); queue.add({ id: 2 }, 5); queue.add({ id: 3 }, 5); queue.add({ id: 4 }, 5); queue.add({ id: 5 }, 5); queue.add({ id: 6 }, 5); queue.add({ id: 7 }, 2); queue.add({ id: 8 }, 2); queue.add({ id: 9 }, 2); }); metatests.test('queue round robin with priority', (test) => { const queue = metasync.queue(3).roundRobin().priority(); const res = []; queue.process((item, callback) => { process.nextTick(() => { res.push(item.id); callback(); }); }); queue.drain(() => { test.strictSame(res, [1, 2, 3, 4, 8, 9, 7, 5, 6]); test.end(); }); queue.add({ id: 1 }, 1, 0); queue.add({ id: 2 }, 1, 0); queue.add({ id: 3 }, 1, 10); queue.add({ id: 4 }, 1, 20); queue.add({ id: 5 }, 2, 0); queue.add({ id: 6 }, 2, 0); queue.add({ id: 7 }, 2, 10); queue.add({ id: 8 }, 2, 20); queue.add({ id: 9 }, 3, 0); }); ================================================ FILE: test/queue.pipe.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('priority / pipe', (test) => { const expectedResult = [2, 8, 6, 4]; const result = []; const q1 = metasync .queue(3) .priority() .process((item, cb) => { setTimeout(cb, 50, item.id % 2 ? new Error('unexpected') : null, item); }); const q2 = metasync .queue(1) .wait(100) .timeout(200) .priority() .process((item, cb) => { result.push(item.id); setTimeout(cb, 90); }); q1.pipe(q2); q2.drain(() => { test.strictSame(result, expectedResult); test.end(); }); q1.add({ id: 1 }, 0); q1.add({ id: 2 }, 0); q1.add({ id: 3 }, 1); q1.add({ id: 4 }, 0); q1.add({ id: 5 }, 0); q1.add({ id: 6 }, 10); q1.add({ id: 7 }, 0); q1.add({ id: 8 }, 100); q1.add({ id: 9 }, 0); }); ================================================ FILE: test/queue.priority.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('fifo / priority', (test) => { const expectedResult = [1, 2, 3, 8, 6, 4, 5, 7, 9]; const result = []; const q = metasync .queue(3) .priority() .process((item, cb) => { result.push(item.id); setTimeout(cb, 100); }); q.drain(() => { test.strictSame(result, expectedResult); test.end(); }); q.add({ id: 1 }, 0); q.add({ id: 2 }, 0); q.add({ id: 3 }, 1); q.add({ id: 4 }, 0); q.add({ id: 5 }, 0); q.add({ id: 6 }, 10); q.add({ id: 7 }, 0); q.add({ id: 8 }, 100); q.add({ id: 9 }, 0); }); ================================================ FILE: test/queue.roundRobin.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('roundRobin', (test) => { const expectedResult = [ 1, 2, 3, 4, 5, 6, 7, 8, 20, 9, 21, 10, 22, 11, 23, 12, 13, 14, 15, 16, 17, 18, 19, ]; const result = []; const q = metasync .queue(3) .roundRobin() .process((item, cb) => { result.push(item.id); setTimeout(cb, 100); }); q.drain(() => { test.strictSame(result, expectedResult); test.end(); }); q.add({ id: 1 }, 1); q.add({ id: 2 }, 2); q.add({ id: 3 }, 3); q.add({ id: 4 }, 4); q.add({ id: 5 }, 1); q.add({ id: 6 }, 2); q.add({ id: 7 }, 3); q.add({ id: 8 }, 4); q.add({ id: 9 }, 2); q.add({ id: 10 }, 2); q.add({ id: 11 }, 2); q.add({ id: 12 }, 2); q.add({ id: 13 }, 2); q.add({ id: 14 }, 2); q.add({ id: 15 }, 2); q.add({ id: 16 }, 2); q.add({ id: 17 }, 2); q.add({ id: 18 }, 2); q.add({ id: 19 }, 2); q.add({ id: 20 }, 1); q.add({ id: 21 }, 1); q.add({ id: 22 }, 1); q.add({ id: 23 }, 1); }); ================================================ FILE: test/throttle.js ================================================ 'use strict'; const metasync = require('..'); const metatests = require('metatests'); metatests.test('throttle', (test) => { let callCount = 0; const fn = (arg1, arg2, ...otherArgs) => { test.strictSame(arg1, 'someVal'); test.strictSame(arg2, 4); test.strictSame(otherArgs, []); callCount++; if (callCount === 2) { test.end(); } }; const throttledFn = metasync.throttle(1, fn, 'someVal', 4); throttledFn(); test.strictSame(callCount, 1); throttledFn(); throttledFn(); test.strictSame(callCount, 1); }); metatests.test('throttle merge args', (test) => { let callCount = 0; const fn = (arg1, arg2, ...otherArgs) => { test.strictSame(arg1, 'someVal'); test.strictSame(arg2, 4); test.strictSame(otherArgs, ['str']); callCount++; if (callCount === 2) { test.end(); } }; const throttledFn = metasync.throttle(1, fn, 'someVal', 4); throttledFn('str'); test.strictSame(callCount, 1); throttledFn('str'); throttledFn('str'); test.strictSame(callCount, 1); }); metatests.test('throttle without arguments for function', (test) => { let callCount = 0; const fn = (...args) => { test.strictSame(args, []); callCount++; if (callCount === 2) { test.end(); } }; const throttledFn = metasync.throttle(1, fn); throttledFn(); test.strictSame(callCount, 1); throttledFn(); throttledFn(); test.strictSame(callCount, 1); }); metatests.test('debounce', (test) => { let count = 0; const fn = (arg1, arg2, ...otherArgs) => { test.strictSame(arg1, 'someVal'); test.strictSame(arg2, 4); test.strictSame(otherArgs, []); count++; test.end(); }; const debouncedFn = metasync.debounce(1, fn, 'someVal', 4); debouncedFn(); debouncedFn(); test.strictSame(count, 0); }); metatests.test('debounce without arguments for function', (test) => { let count = 0; const fn = (...args) => { test.strictSame(args, []); count++; test.end(); }; const debouncedFn = metasync.debounce(1, fn); debouncedFn(); debouncedFn(); test.strictSame(count, 0); }); metatests.test('timeout with sync function', (test) => { const syncFn = (callback) => callback(null, 'someVal'); metasync.timeout(1, syncFn, (err, res, ...args) => { test.error(err); test.strictSame(res, 'someVal'); test.strictSame(args, []); test.end(); }); }); metatests.test('timeout', (test) => { metasync.timeout( 10, (callback) => { setTimeout(() => { callback(null, 'someVal'); }, 0); }, (err, res, ...args) => { test.error(err); test.strictSame(res, 'someVal'); test.strictSame(args, []); test.end(); }, ); }); ================================================ FILE: tests/async-iterator.js ================================================ 'use strict'; const path = require('path'); const { between } = require('metautil'); const nodeVerion = between(process.version, 'v', '.'); const testFilePath = path.join(__dirname, './fixtures/iterator.js'); if (nodeVerion >= 10) require(testFilePath); ================================================ FILE: tests/fixtures/iterator.js ================================================ 'use strict'; const path = require('path'); const { fork } = require('child_process'); const { once } = require('events'); const metatests = require('metatests'); const { AsyncIterator, asyncIter } = require('../../'); const array = [1, 2, 3, 4]; metatests.test('new AsyncIterator() on non Iterable', (test) => { test.throws(() => { new AsyncIterator(2); }, new TypeError('Base is not Iterable')); test.end(); }); metatests.test('AsyncIterator.throttle with min value', (test) => { const expectedMin = 1; const { min } = asyncIter([]).throttle(1, expectedMin); test.strictSame(min, expectedMin); test.end(); }); metatests.test('new AsyncIterator() on AsyncIterable', (test) => { const iterator = array[Symbol.iterator](); const iterable = { [Symbol.asyncIterator]: () => ({ next: async () => iterator.next() }), }; const iter = asyncIter(iterable); test.assert(iter instanceof AsyncIterator); test.end(); }); metatests.test('next returns Promise', (test) => { const iter = asyncIter(array); const item = iter.next(); test.assert(item instanceof Promise); test.end(); }); metatests.test('iter returns an AsyncIterator', (test) => { const iterator = asyncIter(array); test.assert(iterator instanceof AsyncIterator); test.end(); }); metatests.test('AsyncIterator is Iterable', async (test) => { const iterator = asyncIter(array); let sum = 0; for await (const value of iterator) { sum += value; } test.strictSame(sum, 10); test.end(); }); metatests.test('AsyncIterator.count', async (test) => { test.strictSame(await asyncIter(array).count(), array.length); test.end(); }); metatests.test('AsyncIterator.count on consumed iterator', async (test) => { const count = await asyncIter(array).skip(array.length).count(); test.strictSame(count, 0); test.end(); }); metatests.test('AsyncIterator.each', async (test) => { const iterator = asyncIter(array); let sum = 0; await iterator.each((value) => { sum += value; }); test.strictSame(sum, 10); test.end(); }); metatests.test('AsyncIterator.forEach', async (test) => { const iterator = asyncIter(array); let sum = 0; await iterator.forEach((value) => { sum += value; }); test.strictSame(sum, 10); test.end(); }); metatests.test('AsyncIterator.parallel', async (test) => { const iterator = asyncIter(array); let sum = 0; await iterator.parallel((value) => { sum += value; }); test.strictSame(sum, 10); test.end(); }); metatests.test('AsyncIterator.forEach with thisArg ', async (test) => { const iterator = asyncIter(array); const obj = { sum: 0, fn(value) { this.sum += value; }, }; await iterator.forEach(obj.fn, obj); test.strictSame(obj.sum, 10); test.end(); }); metatests.test('AsyncIterator.reduce', async (test) => { test.strictSame( await asyncIter(array).reduce((acc, current) => acc + current, 0), 10, ); test.end(); }); metatests.test('AsyncIterator.reduce with no initialValue', async (test) => { test.strictSame( await asyncIter(array).reduce((acc, current) => acc + current), 10, ); test.end(); }); metatests.test( 'AsyncIterator.reduce with no initialValue on consumed iterator', async (test) => { const iterator = asyncIter(array); await test.rejects( iterator .reduce(() => {}) .then(() => iterator.reduce((acc, current) => acc + current)), new TypeError('Reduce of consumed async iterator with no initial value'), ); }, ); metatests.test('AsyncIterator.map', async (test) => { test.strictSame( await asyncIter(array) .map((value) => value * 2) .toArray(), [2, 4, 6, 8], ); test.end(); }); metatests.test('AsyncIterator.map with thisArg', async (test) => { const obj = { multiplier: 2, mapper(value) { return value * this.multiplier; }, }; test.strictSame( await asyncIter(array).map(obj.mapper, obj).toArray(), [2, 4, 6, 8], ); test.end(); }); metatests.test('AsyncIterator.filter', async (test) => { test.strictSame( await asyncIter(array) .filter((value) => !(value % 2)) .toArray(), [2, 4], ); test.end(); }); metatests.test('AsyncIterator.filter with thisArg', async (test) => { const obj = { divider: 2, predicate(value) { return !(value % this.divider); }, }; test.strictSame( await asyncIter(array).filter(obj.predicate, obj).toArray(), [2, 4], ); test.end(); }); metatests.test('AsyncIterator.flat', async (test) => { const array = [[[[1], 2], 3], 4]; const flatArray = [1, 2, 3, 4]; const newArray = await asyncIter(array).flat(3).toArray(); test.strictSame(newArray, flatArray); test.end(); }); metatests.test('AsyncIterator.flat with no depth', async (test) => { const array = [[[[1], 2], 3], 4]; const flatArray = [[[1], 2], 3, 4]; const newArray = await asyncIter(array).flat().toArray(); test.strictSame(newArray, flatArray); test.end(); }); metatests.test('AsyncIterator.flatMap', async (test) => { const array = [1, 2, 3]; const result = [1, 1, 2, 2, 3, 3]; const newArray = await asyncIter(array) .flatMap((item) => [item, item]) .toArray(); test.strictSame(newArray, result); test.end(); }); metatests.test( 'AsyncIterator.flatMap that returns neither AsyncIterator nor Iterable', async (test) => { const array = [1, 2, 3]; const result = [2, 4, 6]; const newArray = await asyncIter(array) .flatMap((item) => item * 2) .toArray(); test.strictSame(newArray, result); test.end(); }, ); metatests.test('AsyncIterator.flatMap with thisArg', async (test) => { const obj = { value: 1, mapper(item) { return [item, this.value]; }, }; const array = [1, 2, 3]; const result = [1, 1, 2, 1, 3, 1]; test.strictSame( await asyncIter(array).flatMap(obj.mapper, obj).toArray(), result, ); test.end(); }); metatests.test('AsyncIterator.zip', async (test) => { const it = asyncIter(array); const itr = asyncIter(array).take(3); const iterator = asyncIter(array).take(2); const zipIter = it.zip(itr, iterator); test.strictSame(await zipIter.toArray(), [ [1, 1, 1], [2, 2, 2], ]); test.end(); }); metatests.test('AsyncIterator.chain', async (test) => { const it = asyncIter(array).take(1); const itr = await asyncIter(array).skip(1).take(1); const iterator = await asyncIter(array).skip(2).take(2); test.strictSame(await it.chain(itr, iterator).toArray(), [1, 2, 3, 4]); test.end(); }); metatests.test('AsyncIterator.take', async (test) => { const it = asyncIter(array).take(2); test.strictSame((await it.next()).value, 1); test.strictSame((await it.next()).value, 2); test.assert((await it.next()).done); test.end(); }); metatests.testSync('AsyncIterator.takeWhile', async (test) => { const it = asyncIter(array).takeWhile((x) => x < 3); test.strictSame(await it.toArray(), [1, 2]); test.assert(it.next().done); }); metatests.test('AsyncIterator.skip', async (test) => { const it = asyncIter(array).skip(2); test.strictSame((await it.next()).value, 3); test.strictSame((await it.next()).value, 4); test.assert((await it.next()).done); test.end(); }); metatests.test('AsyncIterator.every that must return true', async (test) => { test.assert(await asyncIter(array).every((item) => item > 0)); test.end(); }); metatests.test('AsyncIterator.every that must return false', async (test) => { test.assertNot(await asyncIter(array).every((item) => item % 2)); test.end(); }); metatests.test('AsyncIterator.every with thisArg', async (test) => { const obj = { min: 0, predicate(value) { return value > this.min; }, }; test.assert(await asyncIter(array).every(obj.predicate, obj)); test.end(); }); metatests.test('AsyncIterator.some that must return true', async (test) => { test.assert(await asyncIter(array).some((item) => item % 2)); test.end(); }); metatests.test('AsyncIterator.some that must return false', async (test) => { test.assertNot(await asyncIter(array).some((item) => item < 0)); test.end(); }); metatests.test('AsyncIterator.some with thisArg', async (test) => { const obj = { max: 2, predicate(value) { return value < this.max; }, }; test.assert(await asyncIter(array).some(obj.predicate, obj)); test.end(); }); metatests.testSync( 'AsyncIterator.someCount that must return true', async (test) => { test.assert(await asyncIter(array).someCount((item) => item % 2, 2)); }, ); metatests.testSync( 'AsyncIterator.someCount that must return false', async (test) => { test.assertNot(await asyncIter(array).someCount((item) => item % 2, 3)); test.assertNot(await asyncIter(array).someCount((item) => item < 0, 1)); }, ); metatests.testSync('AsyncIterator.someCount with thisArg', async (test) => { const obj = { max: 3, predicate(value) { return value < this.max; }, }; test.assert(await asyncIter(array).someCount(obj.predicate, 2, obj)); }); metatests.test('AsyncIterator.find that must find an element', async (test) => { test.strictSame(await asyncIter(array).find((item) => item % 2 === 0), 2); test.end(); }); metatests.test( 'AsyncIterator.find that must not find an element', async (test) => { test.strictSame(await asyncIter(array).find((item) => item > 4), undefined); test.end(); }, ); metatests.test('AsyncIterator.find with thisArg', async (test) => { const obj = { divider: 2, predicate(value) { return value % this.divider === 0; }, }; test.strictSame(await asyncIter(array).find(obj.predicate, obj), 2); test.end(); }); metatests.test('AsyncIterator.includes that must return true', async (test) => { test.assert(await asyncIter(array).includes(1)); test.end(); }); metatests.test('AsyncIterator.includes with a NaN', async (test) => { test.assert(await asyncIter([1, 2, NaN]).includes(NaN)); test.end(); }); metatests.test('AsyncIterator.includes must return false', async (test) => { test.assertNot(await asyncIter(array).includes(0)); test.end(); }); metatests.test( 'AsyncIterator.collectTo must collect to given Collection', async (test) => { const set = await asyncIter(array).collectTo(Set); test.strictSame([...set.values()], array); test.end(); }, ); metatests.test('AsyncIterator.toArray must convert to array', async (test) => { test.strictSame(await asyncIter(array).toArray(), array); test.end(); }); metatests.test( 'AsyncIterator.collectWith must collect to a provided object', async (test) => { const set = new Set(); await asyncIter(array).collectWith(set, (obj, item) => obj.add(item)); test.strictSame([...set.values()], array); test.end(); }, ); metatests.test('AsyncIterator.throttle', async (test) => { const pathThrottleFile = path.join(__dirname, './throttle.js'); const child = fork(pathThrottleFile); const [{ sum, ARRAY_SIZE }] = await once(child, 'message'); test.strictSame(sum, ARRAY_SIZE); }); metatests.testSync( 'AsyncIterator.enumerate must return tuples', async (test) => { let i = 0; await asyncIter(array) .enumerate() .forEach((t) => { test.strictSame(t, [i, array[i]]); i++; }); }, ); metatests.testSync( 'AsyncIterator.enumerate must start from 0', async (test) => { const it = asyncIter(array); await it.next(); let i = 0; await it.enumerate().forEach((t) => { test.strictSame(t, [i, array[i + 1]]); i++; }); }, ); metatests.testSync('AsyncIterator.join default', async (test) => { const actual = await asyncIter(array).join(); test.strictSame(actual, '1,2,3,4'); }); metatests.testSync('AsyncIterator.join', async (test) => { const actual = await asyncIter(array).join(', '); test.strictSame(actual, '1, 2, 3, 4'); }); metatests.testSync('AsyncIterator.join with prefix', async (test) => { const actual = await asyncIter(array).join(', ', 'a = '); test.strictSame(actual, 'a = 1, 2, 3, 4'); }); metatests.testSync('AsyncIterator.join with suffix', async (test) => { const actual = await asyncIter(array).join(', ', '', ' => 10'); test.strictSame(actual, '1, 2, 3, 4 => 10'); }); metatests.testSync( 'AsyncIterator.join with prefix and suffix', async (test) => { const actual = await asyncIter(array).join(', ', '[', ']'); test.strictSame(actual, '[1, 2, 3, 4]'); }, ); ================================================ FILE: tests/fixtures/throttle.js ================================================ 'use strict'; const { asyncIter } = require('../../'); const doSmth = (time) => { const begin = Date.now(); while (Date.now() - begin < time); }; const ARRAY_SIZE = 1000; const TIMER_TIME = 10; const ITEM_TIME = 1; const EXPECTED_PERCENT = 0.7; let sum = 0; const arr = new Array(ARRAY_SIZE).fill(1); const iter = asyncIter(arr) .map((number) => { doSmth(ITEM_TIME); sum += number; }) .throttle(EXPECTED_PERCENT); const timer = setInterval(() => doSmth(TIMER_TIME), 0); (async () => { const begin = Date.now(); await iter.toArray(); const allTime = Date.now() - begin; clearInterval(timer); const mapTime = ARRAY_SIZE * ITEM_TIME; const actualPercent = mapTime / allTime; const actualDeviation = Math.abs(actualPercent - EXPECTED_PERCENT); process.send({ actualDeviation, sum, ARRAY_SIZE }); })(); ================================================ FILE: tests/load/benchmark.js ================================================ 'use strict'; const benchmark = {}; module.exports = benchmark; const rpad = (s, char, count) => s + char.repeat(count - s.length); const lpad = (s, char, count) => char.repeat(count - s.length) + s; benchmark.do = (count, tests) => { let fn; let i = 0; const testCount = tests.length; const nextTest = () => { i++; fn = tests.shift(); const result = []; const begin = process.hrtime(); let j = 0; const nextRepeat = () => { fn((err, res) => { if (err) { console.error(err); } j++; result.push(res); if (j < count) { nextRepeat(); } else { const end = process.hrtime(begin); const diff = end[0] * 1e9 + end[1]; const time = lpad(diff.toString(), '.', 15); const name = rpad(fn.name, '.', 25); console.log(name + time + ' nanoseconds'); if (i < testCount) nextTest(); } }); }; nextRepeat(); }; nextTest(); }; ================================================ FILE: tests/load/collect.class.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/collector.class.js'); const CollectClass = (done) => { const dc = metasync.collect(6); dc.done(done); let i = 0; setImmediate(() => dc.pick('uno', ++i * 2)); setImmediate(() => dc.pick('due', ++i * 3)); setImmediate(() => dc.pick('tre', ++i * 5)); setImmediate(() => dc.pick('4th', 'key' + ++i)); setImmediate(() => dc.pick('5th', ++i === 5)); setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); }; benchmark.do(COUNT, [CollectClass]); ================================================ FILE: tests/load/collect.functor.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/collector.functor.js'); const CollectFunctor = (done) => { const dc = metasync.collect(6); dc.done(done); let i = 0; setImmediate(() => dc.pick('uno', ++i * 2)); setImmediate(() => dc.pick('due', ++i * 3)); setImmediate(() => dc.pick('tre', ++i * 5)); setImmediate(() => dc.pick('4th', 'key' + ++i)); setImmediate(() => dc.pick('5th', ++i === 5)); setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); }; benchmark.do(COUNT, [CollectFunctor]); ================================================ FILE: tests/load/collect.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/collector.js'); const CollectPrototype = (done) => { const dc = metasync.collect(6); dc.done(done); let i = 0; setImmediate(() => dc.pick('uno', ++i * 2)); setImmediate(() => dc.pick('due', ++i * 3)); setImmediate(() => dc.pick('tre', ++i * 5)); setImmediate(() => dc.pick('4th', 'key' + ++i)); setImmediate(() => dc.pick('5th', ++i === 5)); setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); }; benchmark.do(COUNT, [CollectPrototype]); ================================================ FILE: tests/load/collect.prototype.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/collector.prototype.js'); const CollectOldPrototype = (done) => { const dc = new metasync.DataCollector(6); dc.on('done', done); let i = 0; setImmediate(() => dc.collect('uno', ++i * 2)); setImmediate(() => dc.collect('due', ++i * 3)); setImmediate(() => dc.collect('tre', ++i * 5)); setImmediate(() => dc.collect('4th', 'key' + ++i)); setImmediate(() => dc.collect('5th', ++i === 5)); setImmediate(() => dc.collect('6th', 'key' + ++i * 2)); }; benchmark.do(COUNT, [CollectOldPrototype]); ================================================ FILE: tests/load/parallel.collect.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../..'); const Collect = (done) => { const dc = metasync.collect(6); dc.done(done); let i = 0; setImmediate(() => dc.pick('uno', ++i * 2)); setImmediate(() => dc.pick('due', ++i * 3)); setImmediate(() => dc.pick('tre', ++i * 5)); setImmediate(() => dc.pick('4th', 'key' + ++i)); setImmediate(() => dc.pick('5th', ++i === 5)); setImmediate(() => dc.pick('6th', 'key' + ++i * 2)); }; benchmark.do(COUNT, [Collect]); ================================================ FILE: tests/load/parallel.compose.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const metasync = require('../..'); const testCompose = (done) => { let i = 0; const p1 = (context, callback) => { setImmediate(() => callback(null, ++i * 2)); }; const p2 = (context, callback) => { setImmediate(() => callback(null, ++i * 3)); }; const p3 = (context, callback) => { setImmediate(() => callback(null, ++i * 5)); }; const p4 = (context, callback) => { setImmediate(() => callback(null, 'key ' + ++i)); }; const p5 = (context, callback) => { setImmediate(() => callback(null, ++i === 5)); }; const p6 = (context, callback) => { setImmediate(() => callback(null, 'key' + ++i * 2)); }; const f1 = metasync([[p1, p2, p3, p4, p5, p6]]); f1(done); }; benchmark.do(COUNT, [testCompose]); ================================================ FILE: tests/load/parallel.promise.js ================================================ 'use strict'; const COUNT = 1000000; const benchmark = require('./benchmark.js'); const PromiseAll = (done) => { let i = 0; const p1 = new Promise((resolve) => { setImmediate(() => resolve({ p1: ++i * 2 })); }); const p2 = new Promise((resolve) => { setImmediate(() => resolve({ p2: ++i * 3 })); }); const p3 = new Promise((resolve) => { setImmediate(() => resolve({ p3: ++i * 5 })); }); const p4 = new Promise((resolve) => { setImmediate(() => resolve({ p4: 'key ' + ++i })); }); const p5 = new Promise((resolve) => { setImmediate(() => resolve({ p5: ++i === 5 })); }); const p6 = new Promise((resolve) => { setImmediate(() => resolve({ p6: 'key' + ++i * 2 })); }); Promise.all([p1, p2, p3, p4, p5, p6]).then((res) => done(null, res)); }; benchmark.do(COUNT, [PromiseAll]); ================================================ FILE: tests/load/poolify.array.js ================================================ 'use strict'; const COUNT = 10000; const GETS = 300; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/poolify.js'); const poolifyArray = (done) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 10, 100, 200); for (let i = 0; i < GETS; i++) { pool((item) => { setImmediate(() => { pool([item]); if (i === GETS - 1) done(); }); }); } }; benchmark.do(COUNT, [poolifyArray]); ================================================ FILE: tests/load/poolify.opt.js ================================================ 'use strict'; const COUNT = 10000; const GETS = 300; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/poolify.opt.js'); const poolifyNoMixin = (done) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 10, 100, 200); for (let i = 0; i < GETS; i++) { pool((item) => { setImmediate(() => { pool([item]); if (i === GETS - 1) done(); }); }); } }; benchmark.do(COUNT, [poolifyNoMixin]); ================================================ FILE: tests/load/poolify.symbol.js ================================================ 'use strict'; const COUNT = 10000; const GETS = 300; const benchmark = require('./benchmark.js'); const metasync = require('../../lib/poolify.symbol.js'); const poolifySymbol = (done) => { const buffer = () => new Uint32Array(128); const pool = metasync.poolify(buffer, 10, 100, 200); for (let i = 0; i < GETS; i++) { pool((item) => { setImmediate(() => { pool(item); if (i === GETS - 1) done(); }); }); } }; benchmark.do(COUNT, [poolifySymbol]); ================================================ FILE: tests/load/run.sh ================================================ echo Parallel execution: concurrency 6 x 1mln node tests/load/parallel.promise.js node tests/load/parallel.compose.js node tests/load/parallel.collect.js echo echo Sequential execution: concurrency 6 x 100k node tests/load/sequential.promise.js node tests/load/sequential.compose.js echo echo Poolify: array vs symbol 300 times node tests/load/poolify.array.js node tests/load/poolify.symbol.js node tests/load/poolify.opt.js echo echo Collector: 1mnl node tests/load/collect.js node tests/load/collect.class.js node tests/load/collect.prototype.js node tests/load/collect.functor.js ================================================ FILE: tests/load/sequential.compose.js ================================================ 'use strict'; const count = 100000; const benchmark = require('./benchmark.js'); const metasync = require('../..'); const composeSequential = (done) => { let i = 0; const p1 = (context, callback) => { setImmediate(() => callback(null, ++i * 2)); }; const p2 = (context, callback) => { setImmediate(() => callback(null, ++i * 3)); }; const p3 = (context, callback) => { setImmediate(() => callback(null, ++i * 5)); }; const p4 = (context, callback) => { setImmediate(() => callback(null, 'key ' + ++i)); }; const p5 = (context, callback) => { setImmediate(() => callback(null, ++i === 5)); }; const p6 = (context, callback) => { setImmediate(() => callback(null, 'key' + ++i * 2)); }; const f1 = metasync([p1, p2, p3, p4, p5, p6]); f1(done); }; benchmark.do(count, [composeSequential]); ================================================ FILE: tests/load/sequential.promise.js ================================================ 'use strict'; const count = 100000; const benchmark = require('./benchmark.js'); const PromiseThen = (done) => { let i = 0; const p1 = new Promise((resolve) => { setImmediate(() => resolve({ p1: ++i * 2 })); }); const p2 = new Promise((resolve) => { setImmediate(() => resolve({ p2: ++i * 3 })); }); const p3 = new Promise((resolve) => { setImmediate(() => resolve({ p3: ++i * 5 })); }); const p4 = new Promise((resolve) => { setImmediate(() => resolve({ p4: 'key ' + ++i })); }); const p5 = new Promise((resolve) => { setImmediate(() => resolve({ p5: ++i === 5 })); }); const p6 = new Promise((resolve) => { setImmediate(() => resolve({ p6: 'key' + ++i * 2 })); }); Promise.resolve() .then(p1) .then(p2) .then(p3) .then(p4) .then(p5) .then(p6) .then((res) => done(null, res)) .catch(console.error); }; benchmark.do(count, [PromiseThen]); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "moduleResolution": "node", "strict": true, "noEmit": true, "baseUrl": ".", "preserveWatchOutput": true }, "include": ["*.d.ts"] }