Full Code of metarhia/metasync for AI

master 8101e1b394c0 cached
93 files
199.9 KB
59.2k tokens
142 symbols
1 requests
Download .txt
Showing preview only (220K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<!--
Thank you for your pull request.
Check following steps to help us land your changes:
Change [ ] to [x] for completed items.
-->

- [ ] 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 <timur.shemsedinov@gmail.com>
Alexey Orlenko <eaglexrlnk@gmail.com>
Vlad Dziuba <dzyubavlad@gmail.com>
Dmytro Nechai <nechaido@gmail.com>
Oleksandr Kovalchuk <anxolerd@outlook.com>
Vladyslav Dukhin <vladyslav.dukhin@gmail.com>
Arthur Myronenko <gibslp69@gmail.com>
Alexey Kachan <alexkachan@mail.ru>


================================================
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>`][function] promise-returning function

_Returns:_ [`<Function>`][function]

Convert Promise-returning to callback-last / error-first contract

### asyncify(fn)

- `fn`: [`<Function>`][function] regular synchronous function

_Returns:_ [`<Function>`][function] with contract: callback-last / error-first

Convert sync function to callback-last / error-first contract

### promiseToCallbackLast(promise, callback)

- `promise`: [`<Promise>`][promise]
- `callback`: [`<Function>`][function]

Convert Promise to callback-last

### promisify(fn)

- `fn`: [`<Function>`][function] callback-last function

_Returns:_ [`<Function>`][function] Promise-returning function

Convert async function to Promise-returning function

### promisifySync(fn)

- `fn`: [`<Function>`][function] regular synchronous function

_Returns:_ [`<Function>`][function] Promise-returning function

Convert sync function to Promise object

### map(items, fn, done)

- `items`: [`<Array>`][array] incoming
- `fn`: [`<Function>`][function] to be executed for each value in the array
  - `current`: `<any>` current element being processed in the array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `value`: `<any>`
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<Array>`][array]

Asynchronous map (iterate parallel)

### filter(items, fn, done)

- `items`: [`<Array>`][array] incoming
- `fn`: [`<Function>`][function] to be executed for each value in the array
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `accepted`: [`<boolean>`][boolean]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<Array>`][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>`][array] incoming
- `fn`: [`<Function>`][function] to be executed for each value in array
  - `previous`: `<any>` value previously returned in the last iteration
  - `current`: `<any>` current element being processed in the array
  - `callback`: [`<Function>`][function] callback for returning value back to
    reduce function
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `data`: `<any>` resulting value
  - `counter`: [`<number>`][number] index of the current element being processed
    in array
  - `items`: [`<Array>`][array] the array reduce was called upon
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<Array>`][array]
- `initial`: `<any>` optional value to be used as first argument in first
  iteration

Asynchronous reduce

### reduceRight(items, fn, done\[, initial\])

- `items`: [`<Array>`][array] incoming
- `fn`: [`<Function>`][function] to be executed for each value in array
  - `previous`: `<any>` value previously returned in the last iteration
  - `current`: `<any>` current element being processed in the array
  - `callback`: [`<Function>`][function] callback for returning value back to
    reduce function
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `data`: `<any>` resulting value
  - `counter`: [`<number>`][number] index of the current element being processed
    in array
  - `items`: [`<Array>`][array] the array reduce was called upon
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<Array>`][array]
- `initial`: `<any>` optional value to be used as first argument in first
  iteration

Asynchronous reduceRight

### each(items, fn, done)

- `items`: [`<Array>`][array] incoming
- `fn`: [`<Function>`][function]
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `items`: [`<Array>`][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>`][array] incoming
- `fn`: [`<Function>`][function]
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `items`: [`<Array>`][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>`][array] incoming
- `fn`: [`<Function>`][function]
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `accepted`: [`<boolean>`][boolean]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: `<any>`

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>`][array] incoming
- `fn`: [`<Function>`][function]
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `accepted`: [`<boolean>`][boolean]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<boolean>`][boolean]

Asynchronous every

### some(items, fn, done)

- `items`: [`<Array>`][array] incoming
- `fn`: [`<Function>`][function]
  - `value`: `<any>` item from items array
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `accepted`: [`<boolean>`][boolean]
- `done`: [`<Function>`][function] on done
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<boolean>`][boolean]

Asynchronous some (iterate in series)

### asyncMap(items, fn\[, options\]\[, done\])

- `items`: [`<Array>`][array] incoming dataset
- `fn`: [`<Function>`][function]
  - `item`: `<any>`
  - `index`: [`<number>`][number]
- `options`: [`<Object>`][object] map params, optional
  - `min`: [`<number>`][number] min number of items in one next call
  - `percent`: [`<number>`][number] ratio of map time to all time
- `done`: [`<Function>`][function] call on done, optional
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: [`<Array>`][array]

Non-blocking synchronous map

### asyncIter(base)

- `base`: [`<Iterable>`][iterable]|[`<AsyncIterable>`][asynciterable] an
  iterable that is wrapped in [`<AsyncIterator>`][asynciterator]

_Returns:_ [`<AsyncIterator>`][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>`][number]|[`<string[]>`][string]

_Returns:_ [`<Collector>`][collector]

Create Collector instance

### class Collector

Data collector

#### Collector.prototype.constructor(expected)

- `expected`: [`<number>`][number]|[`<string[]>`][string] count or keys

Data collector

#### Collector.prototype.collect(key, err, value)

- `key`: [`<string>`][string]
- `err`: [`<Error>`][error]
- `value`: `<any>`

_Returns:_ [`<this>`][this]

Pick or fail key

#### Collector.prototype.pick(key, value)

- `key`: [`<string>`][string]
- `value`: `<any>`

_Returns:_ [`<this>`][this]

Pick key

#### Collector.prototype.fail(key, err)

- `key`: [`<string>`][string]
- `err`: [`<Error>`][error]

_Returns:_ [`<this>`][this]

Fail key

#### Collector.prototype.take(key, fn, args)

- `key`: [`<string>`][string]
- `fn`: [`<Function>`][function]
- `args`: [`<Array>`][array] rest arguments, to be passed in fn

_Returns:_ [`<this>`][this]

Take method result

#### Collector.prototype.timeout(msec)

- `msec`: [`<number>`][number]

_Returns:_ [`<this>`][this]

Set timeout

#### Collector.prototype.done(callback)

- `callback`: [`<Function>`][function]
  - `err`: [`<Error>`][error]
  - `data`: `<any>`

_Returns:_ [`<this>`][this]

Set on done listener

#### Collector.prototype.finalize(key, err, data)

#### Collector.prototype.distinct(value)

- `value`: [`<boolean>`][boolean]

_Returns:_ [`<this>`][this]

Deny or allow unlisted keys

#### Collector.prototype.cancel(err)

#### Collector.prototype.then(fulfill, reject)

### compose(flow)

- `flow`: [`<Function[]>`][function] callback-last / err-first

_Returns:_ [`<Function>`][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>`][number]

Set timeout

#### Composition.prototype.cancel()

Cancel execution where possible

### firstOf(fns, callback)

- `fns`: [`<Function[]>`][function] callback-last / err-first
- `callback`: [`<Function>`][function] on done, err-first

Executes all asynchronous functions and pass first result to callback

### parallel(fns\[, context\], callback)

- `fns`: [`<Function[]>`][function] callback-last / err-first
- `context`: [`<Object>`][object] incoming data, optional
- `callback`: [`<Function>`][function] on done, err-first

Parallel execution

_Example:_

```js
metasync.parallel([f1, f2, f3], (err, data) => {});
```

### sequential(fns\[, context\], callback)

- `fns`: [`<Function[]>`][function] callback-last with err-first contract
- `context`: [`<Object>`][object] incoming data, optional
- `callback`: [`<Function>`][function] err-first on done

Sequential execution

_Example:_

```js
metasync.sequential([f1, f2, f3], (err, data) => {});
```

### runIf(condition\[, defaultVal\], asyncFn, ...args)

- `condition`: `<any>`
- `defaultVal`: `<any>` optional, value that will be returned to callback if
  `condition` is falsy.
- `asyncFn`: [`<Function>`][function] callback-last function that will be
  executed if `condition` if truthy
- `args`: `<any[]>` args to pass to `asyncFn`

Run `asyncFn` if `condition` is truthy, else return `defaultVal` to callback.

### runIfFn(asyncFn, ...args)

- `asyncFn`: [`<Function>`][function] callback-last function that will be
  executed if it is provided
- `args`: `<any[]>` args to pass to `asyncFn`

Run `asyncFn` if it is provided

### class do

#### do.prototype.constructor(fn, ...args)

### toAsync(fn)

- `fn`: [`<Function>`][function] callback-last / err-first

_Returns:_ [`<Function>`][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>`][function] asynchronous
- `args`: [`<Array>`][array] its arguments

Wrap function adding async chain methods

### of(args)

- `args`: [`<Array>`][array]

Applicative f => a -> f a

### concat(fn1, fn2)

- `fn1`: [`<Function>`][function]
- `fn2`: [`<Function>`][function]

Monoid m => a -> a -> a

### fmap(fn1, f)

- `fn1`: [`<Function>`][function]
- `f`: [`<Function>`][function]

Functor f => (a -> b) -> f a -> f b

### ap(fn, funcA)

- `fn`: [`<Function>`][function]
- `funcA`: [`<Function>`][function]

Applicative f => f (a -> b) -> f a -> f b

### memoize(fn)

- `fn`: [`<Function>`][function] sync or async

_Returns:_ [`<Function>`][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>`][string]
- `listener`: [`<Function>`][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>`][string]
- `args`: `<any>` rest arguments

Emit Memoized events

### poolify(factory, min, norm, max)

### queue(concurrency)

- `concurrency`: [`<number>`][number] simultaneous and asynchronously executing
  tasks

_Returns:_ [`<Queue>`][queue]

Create Queue instance

### class Queue

Queue constructor

#### Queue.prototype.constructor(concurrency)

- `concurrency`: [`<number>`][number] asynchronous concurrency

Queue constructor

#### Queue.prototype.wait(msec)

- `msec`: [`<number>`][number] wait timeout for single item

_Returns:_ [`<this>`][this]

Set wait before processing timeout

#### Queue.prototype.throttle(count\[, interval\])

- `count`: [`<number>`][number] item count
- `interval`: [`<number>`][number] per interval, optional default: 1000 msec

_Returns:_ [`<this>`][this]

Throttle to limit throughput

#### Queue.prototype.add(item\[, factor\[, priority\]\])

- `item`: [`<Object>`][object] to be added
- `factor`: [`<number>`][number]|[`<string>`][string] type, source, destination
  or path, optional
- `priority`: [`<number>`][number] optional

_Returns:_ [`<this>`][this]

Add item to queue

#### Queue.prototype.next(task)

- `task`: [`<Array>`][array] next task [item, factor, priority]

_Returns:_ [`<this>`][this]

Process next item

#### Queue.prototype.takeNext()

_Returns:_ [`<this>`][this]

Prepare next item for processing

#### Queue.prototype.pause()

_Returns:_ [`<this>`][this]

Pause queue

This function is not completely implemented yet

#### Queue.prototype.resume()

_Returns:_ [`<this>`][this]

Resume queue

This function is not completely implemented yet

#### Queue.prototype.clear()

_Returns:_ [`<this>`][this]

Clear queue

#### Queue.prototype.timeout(msec, onTimeout)

- `msec`: [`<number>`][number] process timeout for single item
- `onTimeout`: [`<Function>`][function]

_Returns:_ [`<this>`][this]

Set timeout interval and listener

#### Queue.prototype.process(fn)

- `fn`: [`<Function>`][function]
  - `item`: [`<Object>`][object]
  - `callback`: [`<Function>`][function]
    - `err`: [`<Error>`][error]|[`<null>`][null]
    - `result`: `<any>`

_Returns:_ [`<this>`][this]

Set processing function

#### Queue.prototype.done(fn)

- `fn`: [`<Function>`][function] done listener
  - `err`: [`<Error>`][error]|[`<null>`][null]
  - `result`: `<any>`

_Returns:_ [`<this>`][this]

Set listener on processing done

#### Queue.prototype.success(listener)

- `listener`: [`<Function>`][function] on success
  - `item`: `<any>`

_Returns:_ [`<this>`][this]

Set listener on processing success

#### Queue.prototype.failure(listener)

- `listener`: [`<Function>`][function] on failure
  - `err`: [`<Error>`][error]|[`<null>`][null]

_Returns:_ [`<this>`][this]

Set listener on processing error

#### Queue.prototype.drain(listener)

- `listener`: [`<Function>`][function] on drain

_Returns:_ [`<this>`][this]

Set listener on drain Queue

#### Queue.prototype.fifo()

_Returns:_ [`<this>`][this]

Switch to FIFO mode (default for Queue)

#### Queue.prototype.lifo()

_Returns:_ [`<this>`][this]

Switch to LIFO mode

#### Queue.prototype.priority(flag)

- `flag`: [`<boolean>`][boolean] default: true, false will disable priority mode

_Returns:_ [`<this>`][this]

Activate or deactivate priority mode

#### Queue.prototype.roundRobin(flag)

- `flag`: [`<boolean>`][boolean] default: true, false will disable roundRobin
  mode

_Returns:_ [`<this>`][this]

Activate or deactivate round robin mode

#### Queue.prototype.pipe(dest)

- `dest`: [`<Queue>`][queue] destination queue

_Returns:_ [`<this>`][this]

Pipe processed items to different queue

### throttle(timeout, fn, ...args)

- `timeout`: [`<number>`][number] msec interval
- `fn`: [`<Function>`][function] to be throttled
- `args`: [`<Array>`][array] arguments for fn, optional

_Returns:_ [`<Function>`][function]

Get throttling function, executed once per interval

### debounce(timeout, fn, ...args)

- `timeout`: [`<number>`][number] msec
- `fn`: [`<Function>`][function] to be debounced
- `args`: [`<Array>`][array] arguments for fn, optional

Debounce function, delayed execution

### timeout(timeout, fn, callback)

- `timeout`: [`<number>`][number] time interval
- `fn`: [`<Function>`][function] to be executed
- `callback`: [`<Function>`][function] callback(...args), on done
  - `args`: [`<Array>`][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 <Promise>
//   callback <Function>
const promiseToCallbackLast = (promise) => (callback) => {
  promise.then(
    (value) => {
      callback(null, value);
    },
    (reason) => {
      callback(reason);
    },
  );
};

// Convert Promise-returning to callback-last / error-first contract
//   fn <Function> promise-returning function
//
// Returns: <Function>
const callbackify =
  (fn) =>
  (...args) => {
    const callback = args.pop();
    promiseToCallbackLast(fn(...args))(callback);
  };

// Convert sync function to callback-last / error-first contract
//   fn <Function> regular synchronous function
//
// Returns: <Function> 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 <Function> callback-last function
//
// Returns: <Function> 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 <Function> regular synchronous function
//
// Returns: <Function> 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 - <Array>, incoming
//   fn - <Function>, to be executed for each value in the array
//     current - <any>, current element being processed in the array
//     callback - <Function>
//       err - <Error> | <null>
//       value - <any>
//   done - <Function>, on done
//     err - <Error> | <null>
//     result - <Array>
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 - <Array>, incoming dataset
//   fn - <Function>
//     item - <any>
//     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>
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 - <Array>, incoming
//   fn - <Function>, to be executed for each value in the array
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//       accepted - <boolean>
//   done - <Function>, on done
//     err - <Error> | <null>
//     result - <Array>
//
// 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 - <Array>, incoming
//   fn - <Function>, to be executed for each value in array
//     previous - <any>, value previously returned in the last iteration
//     current - <any>, current element being processed in the array
//     callback - <Function>, callback for returning value
//         back to reduce function
//       err - <Error> | <null>
//       data - <any>, 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 - <any>, 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 - <Array>, incoming
//   fn - <Function>, to be executed for each value in array
//     previous - <any>, value previously returned in the last iteration
//     current - <any>, current element being processed in the array
//     callback - <Function>, callback for returning value
//         back to reduce function
//       err - <Error> | <null>
//       data - <any>, 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 - <any>, 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 - <Array>, incoming
//   fn - <Function>
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//   done - <Function>, on done
//     err - <Error> | <null>
//     items - <Array>
//
// 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 - <Array>, incoming
//   fn - <Function>
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//   done - <Function>, on done
//     err - <Error> | <null>
//     items - <Array>
//
// 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 - <Array>, incoming
//   fn - <Function>,
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//       accepted - <boolean>
//   done - <Function>, on done
//     err - <Error> | <null>
//     result - <any>
//
// 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 - <Array>, incoming
//   fn - <Function>,
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//       accepted - <boolean>
//   done - <Function>, on done
//     err - <Error> | <null>
//     result - <boolean>
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 - <Array>, incoming
//   fn - <Function>
//     value - <any>, item from items array
//     callback - <Function>
//       err - <Error> | <null>
//       accepted - <boolean>
//   done - <Function>, on done
//     err - <Error> | <null>
//     result - <boolean>
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 - <Iterable> | <AsyncIterable>, an iterable
//       that is wrapped in <AsyncIterator>
//
// Returns: <AsyncIterator>
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 - <number> | <string[]>, 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 - <number> | <string[]>
//
// Returns: <Collector>
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 - <number> | <string[]>, count or keys
//
// Returns: <Function>, 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 - <Function>
    //     error - <Error> | <null>
    //     data - <Object>
    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 - <number> | <string[]>, 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 - <string>
//   err - <Error>
//   value - <any>
//
// Returns: <this>
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 - <string>
//   value - <any>
//
// Returns: <this>
Collector.prototype.pick = function (key, value) {
  this.collect(key, null, value);
  return this;
};

// Fail key
//   key - <string>
//   err - <Error>
//
// Returns: <this>
Collector.prototype.fail = function (key, err) {
  this.collect(key, err);
  return this;
};

// Take method result
//   key - <string>
//   fn - <Function>
//   args - <Array>, rest arguments, to be passed in fn
//
// Returns: <this>
Collector.prototype.take = function (key, fn, ...args) {
  fn(...args, (err, data) => {
    this.collect(key, err, data);
  });
  return this;
};

// Set timeout
//   msec - <number>
//
// Returns: <this>
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 - <Function>
//     err - <Error>
//     data - <any>
//
// Returns: <this>
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 - <boolean>
//
// Returns: <this>
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 - <number> | <string[]>
//
// Returns: <Collector>
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 - <string>
//   listener - <Function>, 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 - <string>
//   err - <Error> | <null>
Collector.prototype.emit = function (eventName, err, data) {
  const event = this.events[eventName];
  if (event) event(err, data);
};

// Create new DataCollector
// Signature: expected[, timeout]
//   expected - <number>, count of `collect()` calls expected
//   timeout - <number>, collect timeout, optional
//
// Returns: <DataCollector>
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 - <string>, key in result data
//   data - <Object> | <Error>, 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 - <string[]>
//   timeout - <number>, collect timeout, optional
//
// Returns: <DataCollector>
//
// 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 - <string>
//   data - <scalar> | <Object> | <Error>, 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 - <Function[]>, callback-last / err-first
//
// Returns: <Function>, 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 - <number>
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 - <Function[]>, callback-last / err-first
//   callback - <Function>, 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 - <Function[]>, callback-last / err-first
//   context - <Object>, incoming data, optional
//   callback - <Function>, 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 - <Function[]>, callback-last with err-first contract
//   context - <Object>, incoming data, optional
//   callback - <Function>, 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 - <any>
//   defaultVal - <any>, 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 - <any[]>, 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 - <Function>, callback-last function that will be executed if it
//       is provided
//   args - <any[]>, 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 - <Function>, callback-last / err-first
//
// Returns: <Function>
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 - <Function>, asynchronous
//   args - <Array>, 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 - <Array>
const of = (...args) => asAsync((callback) => callback(null, ...args));

// Monoid m => a -> a -> a
//   fn1 - <Function>
//   fn2 - <Function>
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 - <Function>
//   f - <Function>
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 - <Function>
//   funcA - <Function>
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 - <Function>, sync or async
//
// Returns: <Function>, 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 - <string>
//   listener - <Function>, 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 - <string>
//   args - <any>, 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 - <number>, 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 - <number>, wait timeout for single item
//
// Returns: <this>
Queue.prototype.wait = function (msec) {
  this.waitTimeout = msec;
  return this;
};

// Throttle to limit throughput
// Signature: count[, interval]
//   count - <number>, item count
//   interval - <number>, per interval, optional
//       default: 1000 msec
//
// Returns: <this>
Queue.prototype.throttle = function (count, interval = 1000) {
  this.throttleCount = count;
  this.throttleInterval = interval;
  return this;
};

// Add item to queue
// Signature: item[, factor[, priority]]
//   item - <Object>, to be added
//   factor - <number> | <string>, type, source,
//       destination or path, optional
//   priority - <number>, optional
//
// Returns: <this>
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 - <Array>, next task [item, factor, priority]
//
// Returns: <this>
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: <this>
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: <this>
Queue.prototype.pause = function () {
  this.paused = true;
  return this;
};

// Resume queue
// This function is not completely implemented yet
//
// Returns: <this>
Queue.prototype.resume = function () {
  this.paused = false;
  return this;
};

// Clear queue
//
// Returns: <this>
Queue.prototype.clear = function () {
  this.count = 0;
  this.tasks = [];
  this.waiting = [];
  this.factors = {};
  return this;
};

// Set timeout interval and listener
//   msec - <number>, process timeout for single item
//   onTimeout - <Function>
//
// Returns: <this>
Queue.prototype.timeout = function (msec, onTimeout = null) {
  this.processTimeout = msec;
  if (onTimeout) this.onTimeout = onTimeout;
  return this;
};

// Set processing function
//   fn - <Function>
//     item - <Object>
//     callback - <Function>
//       err - <Error> | <null>
//       result - <any>
//
// Returns: <this>
Queue.prototype.process = function (fn) {
  this.onProcess = fn;
  return this;
};

// Set listener on processing done
//   fn - <Function>, done listener
//     err - <Error> | <null>
//     result - <any>
//
// Returns: <this>
Queue.prototype.done = function (fn) {
  this.onDone = fn;
  return this;
};

// Set listener on processing success
//   listener - <Function>, on success
//     item - <any>
//
// Returns: <this>
Queue.prototype.success = function (listener) {
  this.onSuccess = listener;
  return this;
};

// Set listener on processing error
//   listener - <Function>, on failure
//     err - <Error> | <null>
//
// Returns: <this>
Queue.prototype.failure = function (listener) {
  this.onFailure = listener;
  return this;
};

// Set listener on drain Queue
//   listener - <Function>, on drain
//
// Returns: <this>
Queue.prototype.drain = function (listener) {
  this.onDrain = listener;
  return this;
};

// Switch to FIFO mode (default for Queue)
//
// Returns: <this>
Queue.prototype.fifo = function () {
  this.fifoMode = true;
  return this;
};

// Switch to LIFO mode
//
// Returns: <this>
Queue.prototype.lifo = function () {
  this.fifoMode = false;
  return this;
};

// Activate or deactivate priority mode
//   flag - <boolean>, default: true, false will
//       disable priority mode
//
// Returns: <this>
Queue.prototype.priority = function (flag = true) {
  this.priorityMode = flag;
  return this;
};

// Activate or deactivate round robin mode
//   flag - <boolean>, default: true, false will
//       disable roundRobin mode
//
// Returns: <this>
Queue.prototype.roundRobin = function (flag = true) {
  this.roundRobinMode = flag;
  return this;
};

// Pipe processed items to different queue
//   dest - <Queue>, destination queue
//
// Returns: <this>
Queue.prototype.pipe = function (dest) {
  if (dest instanceof Queue) {
    this.success((item) => {
      dest.add(item);
    });
  }
  return this;
};

// Create Queue instance
//   concurrency - <number>, simultaneous and
//       asynchronously executing tasks
//
// Returns: <Queue>
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 - <number>, msec interval
//   fn - <Function>, to be throttled
//   args - <Array>, arguments for fn, optional
//
// Returns: <Function>
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 - <number>, msec
//   fn - <Function>, to be debounced
//   args - <Array>, 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 - <number>, time interval
//   fn - <Function>, to be executed
//   callback - <Function>, callback(...args), on done
//     args - <Array>
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<T = unknown> = (err: Error | null, data?: T) => void;
type AsyncFunction<T = unknown, R = unknown> = (
  data: T,
  callback: Callback<R>,
) => void;
type AsyncFunctionNoData<R = unknown> = (callback: Callback<R>) => 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<T extends (...args: unknown[]) => Promise<unknown>>(
  fn: T,
): (...args: Parameters<T>) => void;

export function asyncify<T extends (...args: unknown[]) => unknown>(
  fn: T,
): (...args: [...Parameters<T>, Callback<ReturnType<T>>]) => void;

export function promiseToCallbackLast<T>(
  promise: Promise<T>,
): (callback: Callback<T>) => void;

export function promisify<T extends (...args: unknown[]) => void>(
  fn: T,
): (
  ...args: Parameters<T> extends [...infer A, Callback<infer R>] ? A : never
) => Promise<R>;

export function promisifySync<T extends (...args: unknown[]) => unknown>(
  fn: T,
): (...args: Parameters<T>) => Promise<ReturnType<T>>;

export interface AsyncMapOptions {
  min?: number;
  percent?: number;
}

export function map<T, R>(
  items: T[],
  fn: (item: T, callback: Callback<R>) => void,
  done: Callback<R[]>,
): void;

export function asyncMap<T, R>(
  items: T[],
  fn: (item: T, index: number) => R,
  options?: AsyncMapOptions | Callback<R[]>,
  done?: Callback<R[]>,
): void;

export function filter<T>(
  items: T[],
  fn: (item: T, callback: Callback<boolean>) => void,
  done: Callback<T[]>,
): void;

export function reduce<T, R>(
  items: T[],
  fn: (
    previous: R,
    current: T,
    callback: Callback<R>,
    counter: number,
    items: T[],
  ) => void,
  done: Callback<R>,
  initial?: R,
): void;

export function reduceRight<T, R>(
  items: T[],
  fn: (
    previous: R,
    current: T,
    callback: Callback<R>,
    counter: number,
    items: T[],
  ) => void,
  done: Callback<R>,
  initial?: R,
): void;

export function each<T>(
  items: T[],
  fn: (item: T, callback: Callback<void>) => void,
  done: Callback<void>,
): void;

export function series<T>(
  items: T[],
  fn: (item: T, callback: Callback<void>) => void,
  done: Callback<T[]>,
): void;

export function find<T>(
  items: T[],
  fn: (item: T, callback: Callback<boolean>) => void,
  done: Callback<T | undefined>,
): void;

export function every<T>(
  items: T[],
  fn: (item: T, callback: Callback<boolean>) => void,
  done: Callback<boolean>,
): void;

export function some<T>(
  items: T[],
  fn: (item: T, callback: Callback<boolean>) => void,
  done: Callback<boolean>,
): void;

export interface Collector {
  expectKeys: Set<string> | null;
  expected: number;
  keys: Set<string>;
  count: number;
  timer: NodeJS.Timeout | null;
  onDone: (err: Error | null, data?: unknown) => void;
  isDistinct: boolean;
  isDone: boolean;
  data: Record<string, unknown>;

  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<T extends (...args: unknown[]) => void>(
  fn: T,
): (...argsCb: [...unknown[], Callback]) => void;

export function asAsync<T extends (...args: unknown[]) => void>(
  fn: T,
  ...args: Parameters<T> 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<unknown, { err: Error | null; data?: unknown }>;
  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<T extends (...args: unknown[]) => void>(
  fn: T,
): Memoized;

export class Memoized {
  constructor();
}

export interface Pool {
  (par?: Callback<unknown> | 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<Array<[unknown, number | string, number]>>;
  factors: Record<string | number, Array<[unknown, number | string, number]>>;
  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<T extends (...args: unknown[]) => void>(
  timeout: number,
  fn: T,
  ...args: unknown[]
): (...pars: unknown[]) => void;

export function debounce<T extends (...args: unknown[]) => void>(
  timeout: number,
  fn: T,
  ...args: unknown[]
): () => void;

export function timeout(
  timeout: number,
  fn: (callback: Callback) => void,
  callback: Callback,
): void;

export interface AsyncIteratorResult<T> {
  done: boolean;
  value: T;
}

export interface AsyncIterator<T = unknown> {
  base: Iterator<T> | AsyncIterator<T>;

  [Symbol.asyncIterator](): AsyncIterator<T>;
  next(): Promise<AsyncIteratorResult<T>>;
  count(): Promise<number>;
  each(
    fn: (value: T) => void | Promise<void>,
    thisArg?: unknown,
  ): Promise<void>;
  forEach(
    fn: (value: T) => void | Promise<void>,
    thisArg?: unknown,
  ): Promise<void>;
  parallel(
    fn: (value: T) => unknown | Promise<unknown>,
    thisArg?: unknown,
  ): Promise<unknown[]>;
  every(
    predicate: (value: T) => boolean | Promise<boolean>,
    thisArg?: unknown,
  ): Promise<boolean>;
  find(
    predicate: (value: T) => boolean | Promise<boolean>,
    thisArg?: unknown,
  ): Promise<T | undefined>;
  includes(element: T): Promise<boolean>;
  reduce<R>(
    reducer: (accumulator: R, value: T) => R | Promise<R>,
    initialValue: R,
  ): Promise<R>;
  reduce<R>(reducer: (accumulator: R, value: T) => R | Promise<R>): Promise<R>;
  some(
    predicate: (value: T) => boolean | Promise<boolean>,
    thisArg?: unknown,
  ): Promise<boolean>;
  someCount(
    predicate: (value: T) => boolean | Promise<boolean>,
    count: number,
    thisArg?: unknown,
  ): Promise<boolean>;
  collectTo<C extends new (arr: T[]) => unknown>(
    CollectionClass: C,
  ): Promise<InstanceType<C>>;
  collectWith(
    obj: unknown,
    collector: (obj: unknown, element: T) => void,
  ): Promise<void>;
  join(sep?: string, prefix?: string, suffix?: string): Promise<string>;
  toArray(): Promise<T[]>;
  map<R>(fn: (value: T) => R | Promise<R>, thisArg?: unknown): AsyncIterator<R>;
  filter(
    predicate: (value: T) => boolean | Promise<boolean>,
    thisArg?: unknown,
  ): AsyncIterator<T>;
  flat(depth?: number): AsyncIterator<unknown>;
  flatMap<R>(
    fn: (value: T) => R | Promise<R> | Iterable<R> | AsyncIterable<R>,
    thisArg?: unknown,
  ): AsyncIterator<R>;
  take(count: number): AsyncIterator<T>;
  takeWhile(
    predicate: (value: T) => boolean | Promise<boolean>,
    thisArg?: unknown,
  ): AsyncIterator<T>;
  skip(amount: number): AsyncIterator<T>;
  zip<U extends unknown[]>(
    ...iterators: { [K in keyof U]: Iterable<U[K]> | AsyncIterable<U[K]> }
  ): AsyncIterator<[T, ...U]>;
  chain<U extends unknown[]>(
    ...iterators: { [K in keyof U]: Iterable<U[K]> | AsyncIterable<U[K]> }
  ): AsyncIterator<T | U[number]>;
  enumerate(): AsyncIterator<[number, T]>;
  throttle(percent?: number, min?: number): AsyncIterator<T>;
}

export function asyncIter<T>(
  base: Iterable<T> | AsyncIterable<T>,
): AsyncIterator<T>;

export class AsyncIterator<T = unknown> {
  constructor(base: Iterable<T> | AsyncIterable<T>);
}


================================================
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 <timur.shemsedinov@gmail.com>",
  "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 - <Function>
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, '<html>Some archaic web here</html>');
    });
  });

  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 re
Download .txt
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
Download .txt
SYMBOL INDEX (142 symbols across 27 files)

FILE: lib/array.js
  constant DEFAULT_OPTIONS (line 36) | const DEFAULT_OPTIONS = { min: 5, percent: 0.7 };
  constant REDUCE_EMPTY_ARR (line 151) | const REDUCE_EMPTY_ARR =
  constant REDUCE_RIGHT_EMPTY_ARR (line 201) | const REDUCE_RIGHT_EMPTY_ARR =

FILE: lib/async-iterator.js
  class AsyncIterator (line 19) | class AsyncIterator {
    method constructor (line 20) | constructor(base) {
    method next (line 28) | async next() {
    method count (line 32) | async count() {
    method each (line 40) | async each(fn, thisArg) {
    method forEach (line 44) | async forEach(fn, thisArg) {
    method parallel (line 50) | async parallel(fn, thisArg) {
    method every (line 58) | async every(predicate, thisArg) {
    method find (line 66) | async find(predicate, thisArg) {
    method includes (line 76) | async includes(element) {
    method reduce (line 85) | async reduce(reducer, initialValue) {
    method some (line 104) | async some(predicate, thisArg) {
    method someCount (line 113) | async someCount(predicate, count, thisArg) {
    method collectTo (line 123) | async collectTo(CollectionClass) {
    method collectWith (line 128) | async collectWith(obj, collector) {
    method join (line 132) | async join(sep = ',', prefix = '', suffix = '') {
    method toArray (line 144) | async toArray() {
    method map (line 152) | map(mapper, thisArg) {
    method filter (line 156) | filter(predicate, thisArg) {
    method flat (line 160) | flat(depth = 1) {
    method flatMap (line 164) | flatMap(mapper, thisArg) {
    method zip (line 168) | zip(...iterators) {
    method chain (line 172) | chain(...iterators) {
    method take (line 176) | take(amount) {
    method takeWhile (line 180) | takeWhile(predicate, thisArg) {
    method skip (line 184) | skip(amount) {
    method throttle (line 191) | throttle(percent, min) {
    method enumerate (line 195) | enumerate() {
  method [Symbol.asyncIterator] (line 24) | [Symbol.asyncIterator]() {
  class MapIterator (line 200) | class MapIterator extends AsyncIterator {
    method constructor (line 201) | constructor(base, mapper, thisArg) {
    method next (line 207) | async next() {
  class FilterIterator (line 216) | class FilterIterator extends AsyncIterator {
    method constructor (line 217) | constructor(base, predicate, thisArg) {
    method next (line 223) | async next() {
  class FlatIterator (line 233) | class FlatIterator extends AsyncIterator {
    method constructor (line 234) | constructor(base, depth) {
    method next (line 241) | async next() {
  class FlatMapIterator (line 268) | class FlatMapIterator extends AsyncIterator {
    method constructor (line 269) | constructor(base, mapper, thisArg) {
    method next (line 276) | async next() {
  class TakeIterator (line 301) | class TakeIterator extends AsyncIterator {
    method constructor (line 302) | constructor(base, amount) {
    method next (line 308) | async next() {
  class TakeWhileIterator (line 317) | class TakeWhileIterator extends AsyncIterator {
    method constructor (line 318) | constructor(base, predicate, thisArg) {
    method next (line 325) | async next() {
  class ZipIterator (line 335) | class ZipIterator extends AsyncIterator {
    method constructor (line 336) | constructor(base, iterators) {
    method next (line 341) | async next() {
  class ChainIterator (line 362) | class ChainIterator extends AsyncIterator {
    method constructor (line 363) | constructor(base, iterators) {
    method next (line 369) | async next() {
  class EnumerateIterator (line 383) | class EnumerateIterator extends AsyncIterator {
    method constructor (line 384) | constructor(base) {
    method next (line 389) | async next() {
  class ThrottleIterator (line 398) | class ThrottleIterator extends AsyncIterator {
    method constructor (line 399) | constructor(base, percent = 0.7, min = 5) {
    method next (line 410) | async next() {

FILE: lib/collector.class.js
  constant UNEXPECTED_KEY (line 14) | const UNEXPECTED_KEY = 'Metasync: unexpected key: ';
  constant COLLECT_TIMEOUT (line 15) | const COLLECT_TIMEOUT = 'Metasync: Collector timed out';
  constant COLLECT_CANCELED (line 16) | const COLLECT_CANCELED = 'Metasync: Collector cancelled';
  class Collector (line 18) | class Collector {
    method constructor (line 21) | constructor(expected) {
    method collect (line 33) | collect(key, err, value) {
    method pick (line 56) | pick(key, value) {
    method fail (line 61) | fail(key, err) {
    method take (line 66) | take(key, fn, ...args) {
    method timeout (line 73) | timeout(msec) {
    method done (line 87) | done(callback) {
    method finalize (line 92) | finalize(key, err, data) {
    method distinct (line 105) | distinct(value = true) {
    method cancel (line 110) | cancel(err) {
    method then (line 116) | then(fulfilled, rejected) {

FILE: lib/collector.functor.js
  constant TYPE_ERROR (line 3) | const TYPE_ERROR = 'Metasync: Collect unexpected type';
  constant COLLECT_TIMEOUT (line 4) | const COLLECT_TIMEOUT = 'Metasync: Collector timed out';

FILE: lib/collector.js
  constant UNEXPECTED_KEY (line 5) | const UNEXPECTED_KEY = 'Metasync: unexpected key: ';
  constant COLLECT_TIMEOUT (line 6) | const COLLECT_TIMEOUT = 'Metasync: Collector timed out';
  constant COLLECT_CANCELED (line 7) | const COLLECT_CANCELED = 'Metasync: Collector cancelled';
  function Collector (line 11) | function Collector(expected) {

FILE: lib/collector.prototype.js
  function Collector (line 8) | function Collector() {}
  constant COLLECT_TIMEOUT (line 10) | const COLLECT_TIMEOUT = 'Metasync: Collector timed out';

FILE: lib/composition.js
  function Composition (line 3) | function Composition() {}
  constant COMPOSE_CANCELED (line 5) | const COMPOSE_CANCELED = 'Metasync: asynchronous composition canceled';
  constant COMPOSE_TIMEOUT (line 6) | const COMPOSE_TIMEOUT = 'Metasync: asynchronous composition timed out';

FILE: lib/do.js
  function Do (line 3) | function Do() {}

FILE: lib/memoize.js
  function Memoized (line 3) | function Memoized() {}

FILE: lib/queue.js
  function Queue (line 5) | function Queue(concurrency) {
  constant QUEUE_TIMEOUT (line 27) | const QUEUE_TIMEOUT = 'Metasync: Queue timed out';

FILE: lib/throttle.js
  constant FN_TIMEOUT (line 53) | const FN_TIMEOUT = 'Metasync: asynchronous function timed out';

FILE: metasync.d.ts
  type Callback (line 1) | type Callback<T = unknown> = (err: Error | null, data?: T) => void;
  type AsyncFunction (line 2) | type AsyncFunction<T = unknown, R = unknown> = (
  type AsyncFunctionNoData (line 6) | type AsyncFunctionNoData<R = unknown> = (callback: Callback<R>) => void;
  type FlowFunction (line 7) | type FlowFunction = AsyncFunction | AsyncFunctionNoData | Flow;
  type Flow (line 9) | type Flow = FlowFunction[];
  type Composition (line 11) | interface Composition {
  class Composition (line 45) | class Composition {
  type AsyncMapOptions (line 71) | interface AsyncMapOptions {
  type Collector (line 151) | interface Collector {
  class Collector (line 183) | class Collector {
  type Do (line 220) | interface Do {
  type MemoizedEvents (line 270) | interface MemoizedEvents {
  type Memoized (line 279) | interface Memoized extends Function {
  class Memoized (line 304) | class Memoized {
  type Pool (line 308) | interface Pool {
  type Queue (line 321) | interface Queue {
  class Queue (line 365) | class Queue {
  type AsyncIteratorResult (line 387) | interface AsyncIteratorResult<T> {
  type AsyncIterator (line 392) | interface AsyncIterator<T = unknown> {
  class AsyncIterator (line 472) | class AsyncIterator<T = unknown> {

FILE: test/compose.js
  constant AC1 (line 86) | const AC1 = 'async functions composition cancel before start';
  constant AC2 (line 120) | const AC2 = 'async functions composition cancel in the middle';

FILE: test/examples.js
  constant ASYNC_TIMEOUT (line 7) | const ASYNC_TIMEOUT = 200;

FILE: test/fp.fmap.js
  constant FP1 (line 44) | const FP1 = 'Getting error with no second argument execution';

FILE: tests/fixtures/iterator.js
  method fn (line 110) | fn(value) {
  method mapper (line 163) | mapper(value) {
  method predicate (line 188) | predicate(value) {
  method mapper (line 244) | mapper(item) {
  method predicate (line 314) | predicate(value) {
  method predicate (line 336) | predicate(value) {
  method predicate (line 363) | predicate(value) {
  method predicate (line 387) | predicate(value) {

FILE: tests/fixtures/throttle.js
  constant ARRAY_SIZE (line 10) | const ARRAY_SIZE = 1000;
  constant TIMER_TIME (line 11) | const TIMER_TIME = 10;
  constant ITEM_TIME (line 12) | const ITEM_TIME = 1;
  constant EXPECTED_PERCENT (line 13) | const EXPECTED_PERCENT = 0.7;

FILE: tests/load/collect.class.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/collect.functor.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/collect.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/collect.prototype.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/parallel.collect.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/parallel.compose.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/parallel.promise.js
  constant COUNT (line 3) | const COUNT = 1000000;

FILE: tests/load/poolify.array.js
  constant COUNT (line 3) | const COUNT = 10000;
  constant GETS (line 4) | const GETS = 300;

FILE: tests/load/poolify.opt.js
  constant COUNT (line 3) | const COUNT = 10000;
  constant GETS (line 4) | const GETS = 300;

FILE: tests/load/poolify.symbol.js
  constant COUNT (line 3) | const COUNT = 10000;
  constant GETS (line 4) | const GETS = 300;
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (218K chars).
[
  {
    "path": ".editorconfig",
    "chars": 207,
    "preview": "# http://editorconfig.org\nroot = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_wh"
  },
  {
    "path": ".gitattributes",
    "chars": 8,
    "preview": "* -text\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 22,
    "preview": "patreon: tshemsedinov\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 598,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 594,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n**Is your feat"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 534,
    "preview": "---\nname: Question\nabout: Please don't open an issue to ask questions\n---\n\nIssues on GitHub are intended to be related t"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 375,
    "preview": "<!--\nThank you for your pull request.\nCheck following steps to help us land your changes:\nChange [ ] to [x] for complete"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 718,
    "preview": "name: Testing CI\non: pull_request\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        node:"
  },
  {
    "path": ".gitignore",
    "chars": 55,
    "preview": "node_modules\ndist\n*.log\n.DS_Store\ncoverage\n.nyc_output\n"
  },
  {
    "path": ".npmignore",
    "chars": 29,
    "preview": "node_modules\n*.log\n.DS_Store\n"
  },
  {
    "path": ".prettierignore",
    "chars": 37,
    "preview": "/dist\npackage.json\npackage-lock.json\n"
  },
  {
    "path": "AUTHORS",
    "chars": 318,
    "preview": "Timur Shemsedinov <timur.shemsedinov@gmail.com>\nAlexey Orlenko <eaglexrlnk@gmail.com>\nVlad Dziuba <dzyubavlad@gmail.com>"
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "MIT License\n\nCopyright (c) 2016-2025 Metarhia contributors\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 22617,
    "preview": "# Asynchronous Programming Library\n\n[![ci status](https://github.com/metarhia/metasync/workflows/Testing%20CI/badge.svg)"
  },
  {
    "path": "eslint.config.js",
    "chars": 175,
    "preview": "'use strict';\n\nconst init = require('eslint-config-metarhia');\n\ninit[0].ignores.push('./dist/**/*.js');\ninit[0].rules['n"
  },
  {
    "path": "lib/adapters.js",
    "chars": 1822,
    "preview": "'use strict';\n\n// Convert Promise to callback-last\n//   promise <Promise>\n//   callback <Function>\nconst promiseToCallba"
  },
  {
    "path": "lib/array.js",
    "chars": 11366,
    "preview": "'use strict';\n\n// Asynchronous map (iterate parallel)\n//   items - <Array>, incoming\n//   fn - <Function>, to be execute"
  },
  {
    "path": "lib/async-iterator.js",
    "chars": 9552,
    "preview": "/* eslint-disable no-use-before-define */\n\n'use strict';\n\nconst { promisify } = require('util');\n\nconst timeout = promis"
  },
  {
    "path": "lib/collector.class.js",
    "chars": 2890,
    "preview": "'use strict';\n\nconst emptiness = () => {};\nconst once = (callback) => {\n  let called = false;\n  return (...args) => {\n  "
  },
  {
    "path": "lib/collector.functor.js",
    "chars": 2139,
    "preview": "'use strict';\n\nconst TYPE_ERROR = 'Metasync: Collect unexpected type';\nconst COLLECT_TIMEOUT = 'Metasync: Collector time"
  },
  {
    "path": "lib/collector.js",
    "chars": 3487,
    "preview": "'use strict';\n\nconst emptiness = () => {};\n\nconst UNEXPECTED_KEY = 'Metasync: unexpected key: ';\nconst COLLECT_TIMEOUT ="
  },
  {
    "path": "lib/collector.prototype.js",
    "chars": 3519,
    "preview": "'use strict';\n\nconst inherits = (child, parent) => {\n  child.prototype = Object.create(parent.prototype);\n  child.protot"
  },
  {
    "path": "lib/composition.js",
    "chars": 5101,
    "preview": "'use strict';\n\nfunction Composition() {}\n\nconst COMPOSE_CANCELED = 'Metasync: asynchronous composition canceled';\nconst "
  },
  {
    "path": "lib/control.js",
    "chars": 3831,
    "preview": "'use strict';\n\nconst once = (callback) => {\n  let called = false;\n  return (...args) => {\n    if (!called) {\n      calle"
  },
  {
    "path": "lib/do.js",
    "chars": 865,
    "preview": "'use strict';\n\nfunction Do() {}\n\nconst chain = function (fn, ...args) {\n  const current = (done) => {\n    if (done) curr"
  },
  {
    "path": "lib/fp.js",
    "chars": 1718,
    "preview": "'use strict';\n\nlet asyncChainMethods = null;\n// Convert synchronous function to asynchronous\n// Transform function with "
  },
  {
    "path": "lib/memoize.js",
    "chars": 2096,
    "preview": "'use strict';\n\nfunction Memoized() {}\n\n// Create memoized function\n//   fn - <Function>, sync or async\n//\n// Returns: <F"
  },
  {
    "path": "lib/poolify.js",
    "chars": 1064,
    "preview": "'use strict';\n\nconst duplicate = (factory, n) => Array.from({ length: n }, factory);\n\nconst provide = (callback) => (ite"
  },
  {
    "path": "lib/poolify.opt.js",
    "chars": 1025,
    "preview": "'use strict';\n\nconst duplicate = (factory, n) => Array.from({ length: n }, factory);\n\nconst provide = (callback) => (ite"
  },
  {
    "path": "lib/poolify.symbol.js",
    "chars": 1132,
    "preview": "'use strict';\n\nconst poolified = Symbol('poolified');\n\nconst mixFlag = { [poolified]: true };\n\nconst duplicate = (factor"
  },
  {
    "path": "lib/queue.js",
    "chars": 6688,
    "preview": "'use strict';\n\n// Queue constructor\n//   concurrency - <number>, asynchronous concurrency\nfunction Queue(concurrency) {\n"
  },
  {
    "path": "lib/throttle.js",
    "chars": 1844,
    "preview": "'use strict';\n\n// Get throttling function, executed once per interval\n// Signature: timeout, fn, ...args\n//   timeout - "
  },
  {
    "path": "metasync.d.ts",
    "chars": 12701,
    "preview": "type Callback<T = unknown> = (err: Error | null, data?: T) => void;\ntype AsyncFunction<T = unknown, R = unknown> = (\n  d"
  },
  {
    "path": "metasync.js",
    "chars": 1181,
    "preview": "'use strict';\n\nconst composition = require('./lib/composition.js');\nconst adapters = require('./lib/adapters.js');\nconst"
  },
  {
    "path": "package.json",
    "chars": 1437,
    "preview": "{\n  \"name\": \"metasync\",\n  \"version\": \"0.3.33\",\n  \"author\": \"Timur Shemsedinov <timur.shemsedinov@gmail.com>\",\n  \"descrip"
  },
  {
    "path": "prettier.config.js",
    "chars": 147,
    "preview": "'use strict';\n\nmodule.exports = {\n  printWidth: 80,\n  singleQuote: true,\n  trailingComma: 'all',\n  tabWidth: 2,\n  useTab"
  },
  {
    "path": "test/adapters.js",
    "chars": 2838,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('callbackify: Pro"
  },
  {
    "path": "test/array.asyncMap.js",
    "chars": 1219,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('succesfull map',"
  },
  {
    "path": "test/array.each.js",
    "chars": 1558,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('successful each'"
  },
  {
    "path": "test/array.every.js",
    "chars": 1876,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nconst identity = (x, callback) ="
  },
  {
    "path": "test/array.filter.js",
    "chars": 1979,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('successful filte"
  },
  {
    "path": "test/array.find.js",
    "chars": 1697,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('find with error'"
  },
  {
    "path": "test/array.map.js",
    "chars": 1194,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('succesfull map',"
  },
  {
    "path": "test/array.reduce.js",
    "chars": 2805,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('reduce with init"
  },
  {
    "path": "test/array.reduceRight.js",
    "chars": 2921,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('reduceRight with"
  },
  {
    "path": "test/array.series.js",
    "chars": 1048,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('successful serie"
  },
  {
    "path": "test/array.some.js",
    "chars": 1276,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('successful some'"
  },
  {
    "path": "test/collectors.js",
    "chars": 5507,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('data collector',"
  },
  {
    "path": "test/compose.clone.js",
    "chars": 771,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('async functions "
  },
  {
    "path": "test/compose.js",
    "chars": 5268,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('async parallel f"
  },
  {
    "path": "test/compose.then.js",
    "chars": 1605,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('successful then'"
  },
  {
    "path": "test/composition.cancel.js",
    "chars": 885,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\n// Emulate Asynchronous calls of"
  },
  {
    "path": "test/composition.js",
    "chars": 1439,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\nconst fs = require('fs');\n\nconst "
  },
  {
    "path": "test/composition.parallel.js",
    "chars": 913,
    "preview": "'use strict';\n\nconst metatests = require('metatests');\nconst metasync = require('..');\n\n// Emulate Asynchronous calls of"
  },
  {
    "path": "test/composition.pause.js",
    "chars": 1558,
    "preview": "'use strict';\n\nconst metatests = require('metatests');\nconst metasync = require('..');\nconst fs = require('fs');\n\nconst "
  },
  {
    "path": "test/composition.sequential.js",
    "chars": 468,
    "preview": "'use strict';\n\nconst metatests = require('metatests');\nconst metasync = require('..');\n\nmetatests.test('sequential funct"
  },
  {
    "path": "test/control.js",
    "chars": 3454,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('firstOf', (test)"
  },
  {
    "path": "test/do.js",
    "chars": 1326,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\n// Emulate Asynchronous calls of"
  },
  {
    "path": "test/examples.js",
    "chars": 10346,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst fs = require('fs');\nconst metatests = require('metatests');\nconst p"
  },
  {
    "path": "test/firstOf.js",
    "chars": 798,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('firstOf', (test)"
  },
  {
    "path": "test/fp.ap.js",
    "chars": 1060,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nconst asyncArgs = (callback) => "
  },
  {
    "path": "test/fp.asAsync.js",
    "chars": 641,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nconst asyncSum = (x, y, callback"
  },
  {
    "path": "test/fp.concat.js",
    "chars": 1191,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nconst asyncData = 'data';\nconst "
  },
  {
    "path": "test/fp.fmap.js",
    "chars": 1911,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nconst asyncData = 'data';\nconst "
  },
  {
    "path": "test/fp.of.js",
    "chars": 285,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('of test', (test)"
  },
  {
    "path": "test/memoize.js",
    "chars": 3908,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('memoize', (test)"
  },
  {
    "path": "test/poolify.js",
    "chars": 2856,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('poolify simple',"
  },
  {
    "path": "test/queue.both.js",
    "chars": 1172,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('priority / round"
  },
  {
    "path": "test/queue.js",
    "chars": 1640,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('queue add', (tes"
  },
  {
    "path": "test/queue.lifo.js",
    "chars": 634,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('lifo / simple', "
  },
  {
    "path": "test/queue.modes.js",
    "chars": 3121,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('queue default FI"
  },
  {
    "path": "test/queue.pipe.js",
    "chars": 866,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('priority / pipe'"
  },
  {
    "path": "test/queue.priority.js",
    "chars": 654,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('fifo / priority'"
  },
  {
    "path": "test/queue.roundRobin.js",
    "chars": 1052,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('roundRobin', (te"
  },
  {
    "path": "test/throttle.js",
    "chars": 2734,
    "preview": "'use strict';\n\nconst metasync = require('..');\nconst metatests = require('metatests');\n\nmetatests.test('throttle', (test"
  },
  {
    "path": "tests/async-iterator.js",
    "chars": 258,
    "preview": "'use strict';\n\nconst path = require('path');\n\nconst { between } = require('metautil');\n\nconst nodeVerion = between(proce"
  },
  {
    "path": "tests/fixtures/iterator.js",
    "chars": 12529,
    "preview": "'use strict';\n\nconst path = require('path');\nconst { fork } = require('child_process');\nconst { once } = require('events"
  },
  {
    "path": "tests/fixtures/throttle.js",
    "chars": 842,
    "preview": "'use strict';\n\nconst { asyncIter } = require('../../');\n\nconst doSmth = (time) => {\n  const begin = Date.now();\n  while "
  },
  {
    "path": "tests/load/benchmark.js",
    "chars": 1007,
    "preview": "'use strict';\n\nconst benchmark = {};\nmodule.exports = benchmark;\n\nconst rpad = (s, char, count) => s + char.repeat(count"
  },
  {
    "path": "tests/load/collect.class.js",
    "chars": 577,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../../lib/"
  },
  {
    "path": "tests/load/collect.functor.js",
    "chars": 583,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../../lib/"
  },
  {
    "path": "tests/load/collect.js",
    "chars": 579,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../../lib/"
  },
  {
    "path": "tests/load/collect.prototype.js",
    "chars": 629,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../../lib/"
  },
  {
    "path": "tests/load/parallel.collect.js",
    "chars": 544,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../..');\n\n"
  },
  {
    "path": "tests/load/parallel.compose.js",
    "chars": 836,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../..');\n\n"
  },
  {
    "path": "tests/load/parallel.promise.js",
    "chars": 832,
    "preview": "'use strict';\n\nconst COUNT = 1000000;\n\nconst benchmark = require('./benchmark.js');\n\nconst PromiseAll = (done) => {\n  le"
  },
  {
    "path": "tests/load/poolify.array.js",
    "chars": 489,
    "preview": "'use strict';\n\nconst COUNT = 10000;\nconst GETS = 300;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = req"
  },
  {
    "path": "tests/load/poolify.opt.js",
    "chars": 497,
    "preview": "'use strict';\n\nconst COUNT = 10000;\nconst GETS = 300;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = req"
  },
  {
    "path": "tests/load/poolify.symbol.js",
    "chars": 496,
    "preview": "'use strict';\n\nconst COUNT = 10000;\nconst GETS = 300;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = req"
  },
  {
    "path": "tests/load/run.sh",
    "chars": 584,
    "preview": "echo Parallel execution: concurrency 6 x 1mln\nnode tests/load/parallel.promise.js\nnode tests/load/parallel.compose.js\nno"
  },
  {
    "path": "tests/load/sequential.compose.js",
    "chars": 845,
    "preview": "'use strict';\n\nconst count = 100000;\n\nconst benchmark = require('./benchmark.js');\nconst metasync = require('../..');\n\nc"
  },
  {
    "path": "tests/load/sequential.promise.js",
    "chars": 927,
    "preview": "'use strict';\n\nconst count = 100000;\n\nconst benchmark = require('./benchmark.js');\n\nconst PromiseThen = (done) => {\n  le"
  },
  {
    "path": "tsconfig.json",
    "chars": 204,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"noEmit\": true,"
  }
]

About this extraction

This page contains the full source code of the metarhia/metasync GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (199.9 KB), approximately 59.2k tokens, and a symbol index with 142 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!