From 304b2b76ea51e7076ddc95ea98238ec12ce010b7 Mon Sep 17 00:00:00 2001 From: Yehor Khilchenko Date: Sun, 17 Mar 2019 21:47:44 +0200 Subject: [PATCH] Add support for iterable objects --- .metadocrc | 1 + README.md | 49 ++--- lib/array.js | 405 ++++++++------------------------------ lib/async-iterator.js | 111 ++++++++--- test/array.asyncMap.js | 56 +++++- test/array.each.js | 46 ++--- test/array.every.js | 34 ++-- test/array.filter.js | 84 ++++---- test/array.find.js | 45 +++-- test/array.map.js | 36 +++- test/array.reduce.js | 67 ++++--- test/array.reduceRight.js | 78 ++++---- test/array.series.js | 27 ++- test/array.some.js | 40 ++-- test/control.js | 11 +- test/firstOf.js | 6 +- 16 files changed, 521 insertions(+), 575 deletions(-) diff --git a/.metadocrc b/.metadocrc index 27245777..7910f8ed 100644 --- a/.metadocrc +++ b/.metadocrc @@ -18,6 +18,7 @@ "lib/throttle.js" ], "customLinks": { + "Iterable": "https://tc39.es/ecma262/#sec-iterable-interface", "AsyncIterable": "https://tc39.github.io/ecma262/#sec-asynciterable-interface", "AsyncIterator": "#class-asynciterator", "ArrayChain": "#class-arraychain", diff --git a/README.md b/README.md index e05baf57..b28fa113 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Convert sync function to Promise object ### map(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] to be executed for each value in the array - `current`: `` current element being processed in the array - `callback`: [``][function] @@ -108,13 +108,13 @@ Convert sync function to Promise object - `value`: `` - `done`: [``][function] on done - `err`: [``][error]|[``][null] - - `result`: [``][array] + - `result`: [``][iterable] Asynchronous map (iterate parallel) ### filter(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] to be executed for each value in the array - `value`: `` item from items array - `callback`: [``][function] @@ -122,9 +122,9 @@ Asynchronous map (iterate parallel) - `accepted`: [``][boolean] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - - `result`: [``][array] + - `result`: [``][iterable] -Asynchrous filter (iterate parallel) +Asynchronous filter (iterate parallel) _Example:_ @@ -138,7 +138,7 @@ metasync.filter( ### reduce(items, fn, done\[, initial\]) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] to be executed for each value in array - `previous`: `` value previously returned in the last iteration - `current`: `` current element being processed in the array @@ -148,10 +148,10 @@ metasync.filter( - `data`: `` resulting value - `counter`: [``][number] index of the current element being processed in array - - `items`: [``][array] the array reduce was called upon -- `done`: [``][function] on done + - `items`: [``][iterable] the array reduce was called upon +- `done`: [``][function] on done, optional - `err`: [``][error]|[``][null] - - `result`: [``][array] + - `result`: [``][iterable] - `initial`: `` optional value to be used as first argument in first iteration @@ -159,7 +159,7 @@ Asynchronous reduce ### reduceRight(items, fn, done\[, initial\]) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] to be executed for each value in array - `previous`: `` value previously returned in the last iteration - `current`: `` current element being processed in the array @@ -169,10 +169,10 @@ Asynchronous reduce - `data`: `` resulting value - `counter`: [``][number] index of the current element being processed in array - - `items`: [``][array] the array reduce was called upon -- `done`: [``][function] on done + - `items`: [``][iterable] the array reduce was called upon +- `done`: [``][function] on done, optional - `err`: [``][error]|[``][null] - - `result`: [``][array] + - `result`: [``][iterable] - `initial`: `` optional value to be used as first argument in first iteration @@ -180,14 +180,14 @@ Asynchronous reduceRight ### each(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - - `items`: [``][array] + - `items`: [``][iterable] Asynchronous each (iterate in parallel) @@ -206,14 +206,14 @@ metasync.each( ### series(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] - `err`: [``][error]|[``][null] - `done`: [``][function] on done - `err`: [``][error]|[``][null] - - `items`: [``][array] + - `items`: [``][iterable] Asynchronous series @@ -234,7 +234,7 @@ metasync.series( ### find(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] @@ -260,7 +260,7 @@ metasync.find( ### every(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] @@ -274,7 +274,7 @@ Asynchronous every ### some(items, fn, done) -- `items`: [``][array] incoming +- `items`: [``][iterable] incoming - `fn`: [``][function] - `value`: `` item from items array - `callback`: [``][function] @@ -288,7 +288,7 @@ Asynchronous some (iterate in series) ### asyncMap(items, fn\[, options\]\[, done\]) -- `items`: [``][array] incoming dataset +- `items`: [``][iterable] incoming dataset - `fn`: [``][function] - `item`: `` - `index`: [``][number] @@ -297,7 +297,7 @@ Asynchronous some (iterate in series) - `percent`: [``][number] ratio of map time to all time - `done`: [``][function] call on done, optional - `err`: [``][error]|[``][null] - - `result`: [``][array] + - `result`: [``][iterable] Non-blocking synchronous map @@ -332,6 +332,8 @@ Create an AsyncIterator instance #### async AsyncIterator.prototype.reduce(reducer, initialValue) +#### async AsyncIterator.prototype.reduceRight(reducer, initialValue) + #### async AsyncIterator.prototype.some(predicate, thisArg) #### async AsyncIterator.prototype.someCount(predicate, count, thisArg) @@ -366,6 +368,8 @@ Create an AsyncIterator instance #### AsyncIterator.prototype.enumerate() +#### AsyncIterator.prototype.reverse() + ### collect(expected) - `expected`: [``][number]|[``][string] @@ -867,6 +871,7 @@ Set timeout for asynchronous function execution - Timur Shemsedinov (marcusaurelius) - See github for full [contributors list](https://github.com/metarhia/metasync/graphs/contributors) +[iterable]: https://tc39.es/ecma262/#sec-iterable-interface [asynciterable]: https://tc39.github.io/ecma262/#sec-asynciterable-interface [asynciterator]: #class-asynciterator [collector]: #class-collector diff --git a/lib/array.js b/lib/array.js index 6f4054ce..0077f262 100644 --- a/lib/array.js +++ b/lib/array.js @@ -1,7 +1,11 @@ 'use strict'; +const common = require('@metarhia/common'); +const { asyncIter } = require('./async-iterator.js'); +const { promisify } = require('util'); + // Asynchronous map (iterate parallel) -// items - , incoming +// items - , incoming // fn - , to be executed for each value in the array // current - , current element being processed in the array // callback - @@ -9,39 +13,18 @@ // value - // done - , on done // err - | -// result - -const map = (items, fn, done) => { - const len = items.length; - if (!len) { - done(null, []); - return; - } - let errored = false; - let count = 0; - const result = new Array(len); - - const next = (index, err, value) => { - if (errored) return; - if (err) { - errored = true; - done(err); - return; - } - result[index] = value; - count++; - if (count === len) done(null, result); - }; - - for (let i = 0; i < len; i++) { - fn(items[i], next.bind(null, i)); - } +// result - +const map = (items, fn, done = common.emptiness) => { + const isArray = Array.isArray(items); + asyncIter(items) + .parallel(promisify(fn)) + .then(res => done(null, isArray ? res : new items.constructor(res))) + .catch(done); }; -const DEFAULT_OPTIONS = { min: 5, percent: 0.7 }; - // Non-blocking synchronous map // Signature: items, fn[, options][, done] -// items - , incoming dataset +// items - , incoming dataset // fn - // item - // index - @@ -50,59 +33,22 @@ const DEFAULT_OPTIONS = { min: 5, percent: 0.7 }; // percent - , ratio of map time to all time // done - , call on done, optional // err - | -// result - -const asyncMap = (items, fn, options = {}, done) => { +// result - +const asyncMap = (items, fn, options = {}, done = common.emptiness) => { if (typeof options === 'function') { done = options; - options = DEFAULT_OPTIONS; - } - - if (!items.length) { - if (done) done(null, []); - return; + options = {}; } - - 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(); + const isArray = Array.isArray(items); + const iter = asyncIter(items) + .map(promisify(fn)) + .throttle(options.percent, options.min); + const collect = isArray ? iter.toArray() : iter.collectTo(items.constructor); + collect.then(res => done(null, res)).catch(done); }; -// Asynchrous filter (iterate parallel) -// items - , incoming +// Asynchronous filter (iterate parallel) +// items - , incoming // fn - , to be executed for each value in the array // value - , item from items array // callback - @@ -110,7 +56,7 @@ const asyncMap = (items, fn, options = {}, done) => { // accepted - // done - , on done // err - | -// result - +// result - // // Example: // metasync.filter( @@ -118,49 +64,22 @@ const asyncMap = (items, fn, options = {}, done) => { // (item, callback) => callback(item.length > 2), // (err, result) => console.dir(result) // ); -const filter = (items, fn, done) => { - const len = items.length; - - if (!len) { - done(null, []); - return; - } - - 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 filter = (items, fn, done = common.emptiness) => { + const isArray = Array.isArray(items); + asyncIter(items) + .parallel(async item => [await promisify(fn)(item), item]) + .then(res => { + const filtered = res + .filter(([predicateResult]) => predicateResult) + .map(([, item]) => item); + done(null, isArray ? filtered : new items.constructor(filtered)); + }) + .catch(done); }; -const REDUCE_EMPTY_ARR = - 'Metasync: reduce of empty array with no initial value'; - // Asynchronous reduce // Signature: items, fn, done[, initial] -// items - , incoming +// items - , incoming // fn - , to be executed for each value in array // previous - , value previously returned in the last iteration // current - , current element being processed in the array @@ -170,55 +89,22 @@ const REDUCE_EMPTY_ARR = // data - , resulting value // counter - , index of the current element // being processed in array -// items - , the array reduce was called upon -// done - , on done +// items - , the array reduce was called upon +// done - , on done, optional // err - | -// result - +// result - // initial - , optional value to be used as first // argument in first iteration -const reduce = (items, fn, done, initial) => { - const len = items.length; - const hasInitial = typeof initial !== 'undefined'; - - if (len === 0 && !hasInitial) { - done(new TypeError(REDUCE_EMPTY_ARR), initial); - return; - } - - let previous = hasInitial ? initial : items[0]; - if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) { - done(null, previous); - return; - } - - let count = hasInitial ? 0 : 1; - let current = items[count]; - const last = len - 1; - - const next = (err, data) => { - if (err) { - done(err); - return; - } - if (count === last) { - done(null, data); - return; - } - count++; - previous = data; - current = items[count]; - fn(previous, current, next, count, items); - }; - - fn(previous, current, next, count, items); +const reduce = (items, fn, done = common.emptiness, initial) => { + asyncIter(items) + .reduce((prev, cur) => promisify(fn)(prev, cur), initial) + .then(res => done(null, res)) + .catch(done); }; -const REDUCE_RIGHT_EMPTY_ARR = - 'Metasync: reduceRight of empty array with no initial value'; - // Asynchronous reduceRight // Signature: items, fn, done[, initial] -// items - , incoming +// items - , incoming // fn - , to be executed for each value in array // previous - , value previously returned in the last iteration // current - , current element being processed in the array @@ -228,58 +114,28 @@ const REDUCE_RIGHT_EMPTY_ARR = // data - , resulting value // counter - , index of the current element // being processed in array -// items - , the array reduce was called upon -// done - , on done +// items - , the array reduce was called upon +// done - , on done, optional // err - | -// result - +// result - // initial - , optional value to be used as first // argument in first iteration -const reduceRight = (items, fn, done, initial) => { - const len = items.length; - const hasInitial = typeof initial !== 'undefined'; - - if (len === 0 && !hasInitial) { - done(new TypeError(REDUCE_RIGHT_EMPTY_ARR), initial); - return; - } - - let previous = hasInitial ? initial : items[len - 1]; - if ((len === 0 && hasInitial) || (len === 1 && !hasInitial)) { - done(null, previous); - return; - } - - let count = hasInitial ? len - 1 : len - 2; - let current = items[count]; - const last = 0; - - const next = (err, data) => { - if (err) { - done(err); - return; - } - if (count === last) { - done(null, data); - return; - } - count--; - previous = data; - current = items[count]; - fn(previous, current, next, count, items); - }; - - fn(previous, current, next, count, items); +const reduceRight = (items, fn, done = common.emptiness, initial) => { + asyncIter(items) + .reduceRight((prev, cur) => promisify(fn)(prev, cur), initial) + .then(res => done(null, res)) + .catch(done); }; // Asynchronous each (iterate in parallel) -// items - , incoming +// items - , incoming // fn - // value - , item from items array // callback - // err - | // done - , on done // err - | -// items - +// items - // // Example: // metasync.each( @@ -290,40 +146,22 @@ const reduceRight = (items, fn, done, initial) => { // }, // (err, data) => console.dir('each done') // ); -const each = (items, fn, done) => { - const len = items.length; - if (len === 0) { - done(null, items); - return; - } - let count = 0; - let errored = false; - - const next = err => { - if (errored) return; - if (err) { - errored = true; - done(err); - return; - } - count++; - if (count === len) done(null); - }; - - for (let i = 0; i < len; i++) { - fn(items[i], next); - } +const each = (items, fn, done = common.emptiness) => { + asyncIter(items) + .parallel(promisify(fn)) + .then(res => done(null, res)) + .catch(done); }; // Asynchronous series -// items - , incoming +// items - , incoming // fn - // value - , item from items array // callback - // err - | // done - , on done // err - | -// items - +// items - // // Example: // metasync.series( @@ -336,29 +174,15 @@ const each = (items, fn, done) => { // console.dir('series done'); // } // ); -const series = (items, fn, done) => { - const len = items.length; - let i = -1; - - const next = () => { - i++; - if (i === len) { - done(null, items); - return; - } - fn(items[i], err => { - if (err) { - done(err); - return; - } - setImmediate(next); - }); - }; - next(); +const series = (items, fn, done = common.emptiness) => { + asyncIter(items) + .each(promisify(fn)) + .then(res => done(null, res)) + .catch(done); }; // Asynchronous find (iterate in series) -// items - , incoming +// items - , incoming // fn - , // value - , item from items array // callback - @@ -376,37 +200,15 @@ const series = (items, fn, done) => { // console.dir(result); // } // ); -const find = (items, fn, done) => { - const len = items.length; - if (len === 0) { - done(); - return; - } - let finished = false; - const last = len - 1; - - const next = (index, err, accepted) => { - if (finished) return; - if (err) { - finished = true; - done(err); - return; - } - if (accepted) { - finished = true; - done(null, items[index]); - return; - } - if (index === last) done(null); - }; - - for (let i = 0; i < len; i++) { - fn(items[i], next.bind(null, i)); - } +const find = (items, fn, done = common.emptiness) => { + asyncIter(items) + .find(promisify(fn)) + .then(res => done(null, res)) + .catch(done); }; // Asynchronous every -// items - , incoming +// items - , incoming // fn - , // value - , item from items array // callback - @@ -415,30 +217,15 @@ const find = (items, fn, done) => { // done - , on done // err - | // result - -const every = (items, fn, done) => { - if (items.length === 0) { - done(null, true); - return; - } - 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); +const every = (items, fn, done = common.emptiness) => { + asyncIter(items) + .parallel(promisify(fn)) + .then(res => done(null, res.every(e => e))) + .catch(done); }; // Asynchronous some (iterate in series) -// items - , incoming +// items - , incoming // fn - // value - , item from items array // callback - @@ -447,31 +234,11 @@ const every = (items, fn, done) => { // done - , on done // err - | // result - -const some = (items, fn, done) => { - const len = items.length; - let i = 0; - - const next = () => { - if (i === len) { - done(null, false); - return; - } - fn(items[i], (err, accepted) => { - if (err) { - done(err); - return; - } - if (accepted) { - done(null, true); - return; - } - i++; - next(); - }); - }; - - if (len > 0) next(); - else done(null, false); +const some = (items, fn, done = common.emptiness) => { + asyncIter(items) + .some(promisify(fn)) + .then(res => done(null, res)) + .catch(done); }; module.exports = { diff --git a/lib/async-iterator.js b/lib/async-iterator.js index d7053455..908fe50a 100644 --- a/lib/async-iterator.js +++ b/lib/async-iterator.js @@ -42,42 +42,53 @@ class AsyncIterator { } async forEach(fn, thisArg) { - for await (const value of this) { - await fn.call(thisArg, value); + let next = await this.next(); + while (!next.done) { + await fn.call(thisArg, next.value); + next = await this.next(); } } async parallel(fn, thisArg) { const promises = []; - for await (const value of this) { - promises.push(fn.call(thisArg, value)); + let next = await this.next(); + while (!next.done) { + promises.push(fn.call(thisArg, next.value)); + next = await this.next(); } return Promise.all(promises); } async every(predicate, thisArg) { - for await (const value of this) { - if (!(await predicate.call(thisArg, value))) { + let next = await this.next(); + while (!next.done) { + if (!(await predicate.call(thisArg, next.value))) { return false; } + next = await this.next(); } return true; } async find(predicate, thisArg) { - for await (const value of this) { - if (await predicate.call(thisArg, value)) { - return value; + let next = await this.next(); + while (!next.done) { + if (await predicate.call(thisArg, next.value)) { + return next.value; } + next = await this.next(); } return undefined; } async includes(element) { - for await (const value of this) { + let next = await this.next(); + while (!next.done) { + const value = next.value; if (value === element || (Number.isNaN(value) && Number.isNaN(element))) { return true; } + next = await this.next(); } return false; } @@ -95,27 +106,37 @@ class AsyncIterator { result = next.value; } - for await (const value of this) { - result = await reducer(result, value); + let next = await this.next(); + while (!next.done) { + result = await reducer(result, next.value); + next = await this.next(); } return result; } + async reduceRight(reducer, initialValue) { + return this.reverse().reduce(reducer, initialValue); + } + async some(predicate, thisArg) { - for await (const value of this) { - if (await predicate.call(thisArg, value)) { + let next = await this.next(); + while (!next.done) { + if (await predicate.call(thisArg, next.value)) { return true; } + next = await this.next(); } return false; } async someCount(predicate, count, thisArg) { let n = 0; - for await (const value of this) { - if (await predicate.call(thisArg, value)) { + let next = await this.next(); + while (!next.done) { + if (await predicate.call(thisArg, next.value)) { if (++n === count) return true; } + next = await this.next(); } return false; } @@ -134,8 +155,10 @@ class AsyncIterator { const { done, value } = await this.next(); if (!done) { result += value; - for await (const value of this) { - result += sep + value; + let next = await this.next(); + while (!next.done) { + result += sep + next.value; + next = await this.next(); } } return result + suffix; @@ -143,8 +166,10 @@ class AsyncIterator { async toArray() { const newArray = []; - for await (const value of this) { - newArray.push(value); + let next = await this.next(); + while (!next.done) { + newArray.push(next.value); + next = await this.next(); } return newArray; } @@ -195,6 +220,10 @@ class AsyncIterator { enumerate() { return new EnumerateIterator(this); } + + reverse() { + return new ReverseIterator(this); + } } class MapIterator extends AsyncIterator { @@ -213,6 +242,28 @@ class MapIterator extends AsyncIterator { } } +class ReverseIterator extends AsyncIterator { + constructor(base) { + super(base); + this.values = null; + this.index = null; + } + + async next() { + if (!this.values) { + this.values = await this.base.toArray(); + this.index = this.values.length - 1; + } + + const index = this.index--; + if (index === -1) { + return { done: true, value: undefined }; + } else { + return { done: false, value: this.values[index] }; + } + } +} + class FilterIterator extends AsyncIterator { constructor(base, predicate, thisArg) { super(base); @@ -221,10 +272,12 @@ class FilterIterator extends AsyncIterator { } async next() { - for await (const value of this.base) { - if (await this.predicate.call(this.thisArg, value)) { - return { done: false, value }; + let next = await this.base.next(); + while (!next.done) { + if (await this.predicate.call(this.thisArg, next.value)) { + return { done: false, value: next.value }; } + next = await this.base.next(); } return { done: true, value: undefined }; } @@ -403,19 +456,19 @@ class ThrottleIterator extends AsyncIterator { this.ratio = percent / (1 - percent); this.sum = 0; - this.count = 0; + this.iterCount = 0; this.begin = Date.now(); this.iterMax = this.min; } async next() { - if (this.iterMax > this.count) { - this.count++; + if (this.iterMax > this.iterCount) { + this.iterCount++; return this.base.next(); } this.sum += Date.now() - this.begin; - const itemTime = this.sum / this.count; + const itemTime = this.sum / this.iterCount; this.begin = Date.now(); await timeout(); @@ -423,9 +476,9 @@ class ThrottleIterator extends AsyncIterator { const number = Math.max((this.ratio * loopTime) / itemTime, this.min); - this.iterMax = Math.round(number) + this.count; + this.iterMax = Math.round(number) + this.iterCount; - this.count++; + this.iterCount++; this.begin = Date.now(); return this.base.next(); } diff --git a/test/array.asyncMap.js b/test/array.asyncMap.js index a2deabc3..b4f27382 100644 --- a/test/array.asyncMap.js +++ b/test/array.asyncMap.js @@ -3,18 +3,32 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('succesfull map', test => { - test.plan(2); - +metatests.test('successful asyncMap with array', test => { const arr = [1, 2, 3]; const expectedArr = [2, 4, 6]; metasync.asyncMap( arr, - item => item * 2, + (item, callback) => process.nextTick(() => callback(null, item * 2)), (err, newArr) => { test.error(err); test.strictSame(newArr, expectedArr); + test.end(); + } + ); +}); + +metatests.test('successful asyncMap with another iterable', test => { + const set = new Set([1, 2, 3]); + const expectedSet = new Set([2, 4, 6]); + + metasync.asyncMap( + set, + (item, callback) => process.nextTick(() => callback(null, item * 2)), + (err, newSet) => { + test.error(err); + test.strictSame([...newSet], [...expectedSet]); + test.end(); } ); }); @@ -24,7 +38,7 @@ const doSmth = time => { while (Date.now() - begin < time); }; -metatests.test('Non-blocking', test => { +metatests.test('asyncMap non-blocking', test => { const ITEM_TIME = 1; const TIMER_TIME = 9; const ARRAY_SIZE = 1000; @@ -38,7 +52,10 @@ metatests.test('Non-blocking', test => { const begin = Date.now(); metasync.asyncMap( arr, - () => doSmth(ITEM_TIME), + (x, callback) => { + doSmth(ITEM_TIME); + callback(); + }, { percent: EXPECTED_PERCENT }, () => { clearInterval(timer); @@ -52,3 +69,30 @@ metatests.test('Non-blocking', test => { } ); }); + +metatests.test('asyncMap with error', test => { + const arr = [1, 2, 3]; + const asyncMapError = new Error('asyncMap error'); + + metasync.asyncMap( + arr, + (x, callback) => + process.nextTick(() => callback(x === 2 ? asyncMapError : null, x * x)), + (err, res) => { + test.isError(err, asyncMapError); + test.assertNot(res); + test.end(); + } + ); +}); + +metatests.test('asyncMap with not iterable', test => { + const obj = { a: '1', b: '2', c: '3' }; + + test.throws( + () => metasync.asyncMap(obj, test.mustNotCall(), test.mustNotCall()), + new TypeError('Base is not Iterable') + ); + + test.end(); +}); diff --git a/test/array.each.js b/test/array.each.js index 22b2c4a2..fce3e386 100644 --- a/test/array.each.js +++ b/test/array.each.js @@ -3,7 +3,7 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('successful each', test => { +metatests.test('successful each with array', test => { const arr = [1, 2, 3, 4]; const elementsSet = new Set(); @@ -24,50 +24,44 @@ metatests.test('successful each', test => { ); }); -metatests.test('each with empty array', test => { - const arr = []; - - const elementsSet = new Set(); - const expectedElementsSet = new Set(arr); +metatests.test('successful each with another iterable', test => { + const map = new Map([[1, 'a'], [2, 'b'], [3, 'c']]); + const mapCopy = new Map(); metasync.each( - arr, - (el, callback) => + map, + (entry, callback) => process.nextTick(() => { - elementsSet.add(el); + mapCopy.set(...entry); callback(null); }), err => { test.error(err); - test.strictSame(elementsSet, expectedElementsSet); + test.strictSame([...mapCopy], [...map]); test.end(); } ); }); +metatests.test('each with empty', test => { + const arr = []; + + metasync.each(arr, test.mustNotCall(), err => { + test.error(err); + 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); - } - }), + (item, callback) => + process.nextTick(() => callback(item === 2 ? eachError : null)), err => { - test.strictSame(err, eachError); - test.strictSame(elementsSet.size, expectedElementsCount); + test.isError(err, eachError); test.end(); } ); diff --git a/test/array.every.js b/test/array.every.js index fb36a173..490804f2 100644 --- a/test/array.every.js +++ b/test/array.every.js @@ -26,23 +26,23 @@ const fewStrictSameResult = (inOutPairs, test) => { metatests.test('every with error', test => { const data = [1, 2, 3]; - const everyErr = new Error('Every error'); + const everyError = new Error('Every error'); const predicate = (item, callback) => { process.nextTick(() => - item % 2 === 0 ? callback(everyErr) : callback(null, true) + item % 2 === 0 ? callback(everyError) : callback(null, true) ); }; metasync.every(data, predicate, err => { - test.strictSame(err, everyErr); + test.isError(err, everyError); test.end(); }); }); -metatests.test('every with empty array', test => - strictSameResult([], true, test, () => test.end()) -); +metatests.test('every with empty', test => { + strictSameResult([], true, test, () => test.end()); +}); metatests.test('every with one-element arrays', test => fewStrictSameResult([[[false], false], [[true], true]], test) @@ -60,14 +60,26 @@ metatests.test('every with two-element arrays', test => ) ); -metatests.test('every', test => { - const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; +const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; - const predicate = (item, callback) => { +metatests.test('every with array', test => { + const predicate = (item, callback) => + process.nextTick(() => callback(null, item > 0)); + + metasync.every(arr, predicate, (err, result) => { + test.error(err); + test.strictSame(result, true); + test.end(); + }); +}); + +metatests.test('every with another iterable', test => { + const set = new Set(arr); + + const predicate = (item, callback) => process.nextTick(() => callback(null, item > 0)); - }; - metasync.every(data, predicate, (err, result) => { + metasync.every(set, predicate, (err, result) => { test.error(err); test.strictSame(result, true); test.end(); diff --git a/test/array.filter.js b/test/array.filter.js index 55528e85..0f56dce3 100644 --- a/test/array.filter.js +++ b/test/array.filter.js @@ -3,7 +3,7 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('successful filter', test => { +metatests.test('successful filter with array', test => { const arr = [ 'Lorem', 'ipsum', @@ -50,69 +50,55 @@ metatests.test('successful filter', test => { ); }); -metatests.test('filter with empty array', test => { - const arr = []; - const expectedArr = []; +metatests.test('successful filter with another iterable', test => { + const set = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const expectedSet = new Set([2, 4, 6, 8, 10]); metasync.filter( - arr, - (str, callback) => process.nextTick(() => callback(null, str.length < 6)), + set, + (x, callback) => process.nextTick(() => callback(null, x % 2 === 0)), (err, res) => { test.error(err); - test.strictSame(res, expectedArr); + test.strictSame([...res], [...expectedSet]); 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', - ]; +metatests.test('filter with empty', test => { + const arr = []; + const expectedArr = []; + + metasync.filter(arr, test.mustNotCall(), (err, res) => { + test.error(err); + test.strictSame(res, expectedArr); + test.end(); + }); +}); + +metatests.test('filter with error', test => { + const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 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); - }), + (x, callback) => + process.nextTick(() => callback(x === 5 ? filterError : null, x % 2)), (err, res) => { - test.error(err); - test.same(res.join(), expectedArr.join()); + test.isError(err, filterError); + test.assertNot(res); test.end(); } ); }); + +metatests.test('filter with not iterable', test => { + const obj = { a: '1', b: '2', c: '3' }; + + test.throws( + () => metasync.filter(obj, test.mustNotCall(), test.mustNotCall()), + new TypeError('Base is not Iterable') + ); + + test.end(); +}); diff --git a/test/array.find.js b/test/array.find.js index fa26a458..3e61dbb7 100644 --- a/test/array.find.js +++ b/test/array.find.js @@ -3,6 +3,8 @@ const metasync = require('..'); const metatests = require('metatests'); +const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + metatests.test('find with error', test => { const data = [1, 2, 3]; const expectedErrorMessage = 'Intentional error'; @@ -22,35 +24,42 @@ metatests.test('find with error', test => { }); }); -metatests.test('find', test => { - const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; +metatests.test('find with array', test => { 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}`); + metasync.find(arr, predicate, (err, result) => { + test.error(err); + test.strictSame(result, 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('find with another iterable', test => { + const map = new Map([[1, 'a'], [2, 'b'], [3, 'c']]); + const expected = [3, 'c']; + const predicate = (item, callback) => + process.nextTick(() => callback(null, item[1] === 'c')); + + metasync.find(map, predicate, (err, result) => { + test.error(err); + test.strictSame(result, expected); + test.end(); + }); +}); + +metatests.test('find with empty', test => { + metasync.find([], test.mustNotCall(), (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]; +metatests.test('find without the desired element', test => { metasync.find( - data, + arr, (el, callback) => process.nextTick(() => callback(null, el === 20)), (err, result) => { test.error(err); diff --git a/test/array.map.js b/test/array.map.js index f059774a..d1cf0e7b 100644 --- a/test/array.map.js +++ b/test/array.map.js @@ -3,7 +3,7 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('succesfull map', test => { +metatests.test('successful map with array', test => { const arr = [1, 2, 3]; const expectedArr = [1, 4, 9]; @@ -18,21 +18,32 @@ metatests.test('succesfull map', test => { ); }); -metatests.test('map with empty array', test => { - const arr = []; - const expectedArr = []; +metatests.test('successful map with another iterable', test => { + const set = new Set([1, 2, 3]); + const expectedSet = new Set([1, 4, 9]); metasync.map( - arr, + set, (x, callback) => process.nextTick(() => callback(null, x * x)), (err, res) => { test.error(err); - test.strictSame(res, expectedArr); + test.strictSame([...res], [...expectedSet]); test.end(); } ); }); +metatests.test('map with empty', test => { + const arr = []; + const expectedArr = []; + + metasync.map(arr, test.mustNotCall(), (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'); @@ -50,9 +61,20 @@ metatests.test('map with error', test => { callback(null, x * x); }), (err, res) => { - test.strictSame(err, mapError); + test.isError(err, mapError); test.strictSame(res, undefined); test.end(); } ); }); + +metatests.test('map with not iterable', test => { + const obj = { a: '1', b: '2', c: '3' }; + + test.throws( + () => metasync.map(obj, test.mustNotCall(), test.mustNotCall()), + new TypeError('Base is not Iterable') + ); + + test.end(); +}); diff --git a/test/array.reduce.js b/test/array.reduce.js index 21028eac..2e6dac20 100644 --- a/test/array.reduce.js +++ b/test/array.reduce.js @@ -3,7 +3,7 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('reduce with initial', test => { +metatests.test('reduce with initial and array', test => { const arr = [1, 2, 3, 4, 5]; const initial = 10; const expectedRes = 25; @@ -20,13 +20,13 @@ metatests.test('reduce with initial', test => { ); }); -metatests.test('reduce with initial and empty array', test => { - const arr = []; +metatests.test('reduce with initial and another iterable', test => { + const set = new Set([1, 2, 3, 4, 5]); const initial = 10; - const expectedRes = 10; + const expectedRes = 25; metasync.reduce( - arr, + set, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { test.error(err); @@ -37,35 +37,44 @@ metatests.test('reduce with initial and empty array', test => { ); }); -metatests.test('reduce without initial and with empty array', test => { +metatests.test('reduce empty items with initial', test => { const arr = []; - const expectedError = new TypeError( - 'Reduce of empty array with no initial value' - ); + const initial = 10; + const expectedRes = 10; metasync.reduce( arr, - (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), + test.mustNotCall(), (err, res) => { - test.strictSame(err, expectedError); - test.strictSame(res, undefined); + test.error(err); + test.strictSame(res, expectedRes); test.end(); - } + }, + initial ); }); -metatests.test('reduce without initial and with single-element array', test => { +metatests.test('reduce empty items without initial', test => { + const arr = []; + const expectedError = new TypeError( + 'Reduce of consumed async iterator with no initial value' + ); + + metasync.reduce(arr, test.mustNotCall(), (err, res) => { + test.isError(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(); - } - ); + metasync.reduce(arr, test.mustNotCall(), (err, res) => { + test.error(err); + test.strictSame(res, 2); + test.end(); + }); }); metatests.test('reduce without initial', test => { @@ -83,12 +92,12 @@ metatests.test('reduce without initial', test => { ); }); -metatests.test('reduce with asymetric function', test => { - const arr = '10110011'; +metatests.test('reduce with asymmetric function', test => { + const str = '10110011'; const expectedRes = 179; metasync.reduce( - arr, + str, (prev, cur, callback) => process.nextTick(() => callback(null, prev * 2 + +cur)), (err, res) => { @@ -100,11 +109,11 @@ metatests.test('reduce with asymetric function', test => { }); metatests.test('reduce with error', test => { - const arr = '10120011'; + const str = '10120011'; const reduceError = new Error('Reduce error'); metasync.reduce( - arr, + str, (prev, cur, callback) => process.nextTick(() => { const digit = +cur; @@ -115,7 +124,7 @@ metatests.test('reduce with error', test => { callback(null, prev * 2 + digit); }), (err, res) => { - test.strictSame(err, reduceError); + test.isError(err, reduceError); test.strictSame(res, undefined); test.end(); } diff --git a/test/array.reduceRight.js b/test/array.reduceRight.js index a3fe9da9..19fa77fb 100644 --- a/test/array.reduceRight.js +++ b/test/array.reduceRight.js @@ -3,7 +3,7 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('reduceRight with initial', test => { +metatests.test('reduceRight with initial and array', test => { const arr = [1, 2, 3, 4, 5]; const initial = 10; const expectedRes = 25; @@ -20,14 +20,32 @@ metatests.test('reduceRight with initial', test => { ); }); -metatests.test('reduceRight with initial and empty array', test => { +metatests.test('reduceRight with initial and another iterable', test => { + const map = new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]]); + const initial = 10; + const expectedRes = 25; + + metasync.reduceRight( + map, + (prev, cur, callback) => + process.nextTick(() => callback(null, prev + cur[1])), + (err, res) => { + test.error(err); + test.strictSame(res, expectedRes); + test.end(); + }, + initial + ); +}); + +metatests.test('reduceRight empty items with initial', test => { const arr = []; const initial = 10; const expectedRes = 10; metasync.reduceRight( arr, - (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), + test.mustNotCall(), (err, res) => { test.error(err); test.strictSame(res, expectedRes); @@ -37,41 +55,33 @@ metatests.test('reduceRight with initial and empty array', test => { ); }); -metatests.test('reduceRight without initial and with empty array', test => { +metatests.test('reduceRight empty items without initial', test => { const arr = []; const expectedError = new TypeError( - 'Reduce of empty array with no initial value' + 'Reduce of consumed async iterator with no initial value' ); + metasync.reduceRight(arr, test.mustNotCall(), (err, res) => { + test.isError(err, expectedError); + test.strictSame(res, undefined); + test.end(); + }); +}); + +metatests.test('reduceRight single-element array without initial', test => { + const arr = [2]; + metasync.reduceRight( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { - test.strictSame(err, expectedError); - test.strictSame(res, undefined); + test.error(err); + test.strictSame(res, 2); 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; @@ -87,12 +97,12 @@ metatests.test('reduceRight without initial', test => { ); }); -metatests.test('reduceRight with asymetric function', test => { - const arr = '10110011'; +metatests.test('reduceRight with asymmetric function', test => { + const str = '10110011'; const expectedRes = 205; metasync.reduceRight( - arr, + str, (prev, cur, callback) => process.nextTick(() => callback(null, prev * 2 + +cur)), (err, res) => { @@ -104,22 +114,22 @@ metatests.test('reduceRight with asymetric function', test => { }); metatests.test('reduceRight with error', test => { - const arr = '10120011'; - const reduceError = new Error('Reduce error'); + const str = '10120011'; + const reduceRightError = new Error('reduceRight error'); - metasync.reduce( - arr, + metasync.reduceRight( + str, (prev, cur, callback) => process.nextTick(() => { const digit = +cur; if (digit > 1) { - callback(reduceError); + callback(reduceRightError); return; } callback(null, prev * 2 + digit); }), (err, res) => { - test.strictSame(err, reduceError); + test.isError(err, reduceRightError); test.strictSame(res, undefined); test.end(); } diff --git a/test/array.series.js b/test/array.series.js index 5171d893..b5398637 100644 --- a/test/array.series.js +++ b/test/array.series.js @@ -3,10 +3,11 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('successful series', test => { +metatests.test('successful series with array', test => { const arr = [1, 2, 3, 4]; const expectedElements = arr; const elements = []; + metasync.series( arr, (el, callback) => { @@ -21,6 +22,25 @@ metatests.test('successful series', test => { ); }); +metatests.test('successful series with another iterable', test => { + const set = new Set([1, 2, 3, 4]); + const expectedElements = set; + const elements = []; + + metasync.series( + set, + (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]; @@ -28,7 +48,7 @@ metatests.test('series with error', test => { const elements = []; let count = 0; - const seriesError = new Error('seriesError'); + const seriesError = new Error('Series error'); metasync.series( arr, @@ -42,8 +62,9 @@ metatests.test('series with error', test => { } }, err => { - test.strictSame(err, seriesError); + test.isError(err, seriesError); test.strictSame(elements, expectedElements); + test.strictSame(count, expectedElementsCount); test.end(); } ); diff --git a/test/array.some.js b/test/array.some.js index bb12bcea..eee39fdb 100644 --- a/test/array.some.js +++ b/test/array.some.js @@ -3,10 +3,10 @@ const metasync = require('..'); const metatests = require('metatests'); -metatests.test('successful some', test => { +metatests.test('successful some with array', 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); @@ -14,6 +14,27 @@ metatests.test('successful some', test => { }); }); +metatests.test('successful some with another iterable', test => { + const set = new Set([1, 2, 3]); + const predicate = (x, callback) => callback(null, x % 2 === 0); + + metasync.some(set, predicate, (err, accepted) => { + test.error(err); + test.strictSame(accepted, true); + test.end(); + }); +}); + +metatests.test('some with empty', test => { + const arr = []; + + metasync.some(arr, test.mustNotCall(), (err, accepted) => { + test.error(err); + test.strictSame(accepted, false); + test.end(); + }); +}); + metatests.test('failing some', test => { const arr = [1, 2, 3]; @@ -25,26 +46,15 @@ metatests.test('failing some', test => { }); }); -metatests.test('erroneous some', test => { +metatests.test('some with error', 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.isError(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(); - }); -}); diff --git a/test/control.js b/test/control.js index a56761d6..931e79e9 100644 --- a/test/control.js +++ b/test/control.js @@ -7,11 +7,11 @@ metatests.test('firstOf', test => { const returningFnIndex = 2; let dataReturned = false; - const execUnlessDataReturned = data => callback => { + const execUnlessDataReturned = (data, callback) => { if (dataReturned) { callback(null, data); } else { - process.nextTick(execUnlessDataReturned); + process.nextTick(() => execUnlessDataReturned(data, callback)); } }; const makeIFn = i => callback => @@ -21,7 +21,7 @@ metatests.test('firstOf', test => { dataReturned = true; callback(null, iData); } else { - execUnlessDataReturned(iData); + execUnlessDataReturned(iData, callback); } }); @@ -136,7 +136,6 @@ metatests.test('runIf forward an error', test => { }); metatests.test('runIfFn', test => { - test.plan(5); const value = 42; const asyncFn = cb => { cb(null, value); @@ -145,10 +144,14 @@ metatests.test('runIfFn', test => { metasync.runIfFn(test.mustCall(asyncFn), (err, res) => { test.error(err); test.strictSame(res, value); + test.end(); }); +}); +metatests.test('runIfFn without fn', test => { metasync.runIfFn(null, (err, res) => { test.error(err); test.assertNot(res); + test.end(); }); }); diff --git a/test/firstOf.js b/test/firstOf.js index 25fb3864..a4a99aeb 100644 --- a/test/firstOf.js +++ b/test/firstOf.js @@ -7,11 +7,11 @@ metatests.test('firstOf', test => { const returningFnIndex = 2; let dataReturned = false; - const execUnlessDataReturned = data => callback => { + const execUnlessDataReturned = (data, callback) => { if (dataReturned) { callback(null, data); } else { - process.nextTick(execUnlessDataReturned); + process.nextTick(() => execUnlessDataReturned(data, callback)); } }; const makeIFn = i => callback => @@ -21,7 +21,7 @@ metatests.test('firstOf', test => { dataReturned = true; callback(null, iData); } else { - execUnlessDataReturned(iData); + execUnlessDataReturned(iData, callback); } });