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..9246dd22 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,29 @@ 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 = common + .iter(res) + .filterMap( + ([predicateResult, item]) => (predicateResult ? item : false), + null, + false + ); + done( + null, + isArray ? filtered.toArray() : 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 +96,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 +121,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 +153,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 +181,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 +207,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 +224,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 +241,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/package-lock.json b/package-lock.json index b59ba8e0..cfcaec63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -818,9 +818,9 @@ } }, "@metarhia/common": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@metarhia/common/-/common-1.4.2.tgz", - "integrity": "sha512-DV6MjZJLaqsn+LdZaUEfyFjnC2TPY1PpfKgUYen7lcwdGdHOrgaOdXGKisDahUM7qGttM9IS6//E1ucGS7XefQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@metarhia/common/-/common-2.1.0.tgz", + "integrity": "sha512-HmIb98pFBlnKN44z1HVugBmsHrLJWA2tBsP3iLh/ehg0PVkNUHOQhDV2tI2NvbCJeTlmPrZ8rdw0BcfPW1q47Q==" }, "@metarhia/doc": { "version": "0.5.3", @@ -3156,7 +3156,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -3246,6 +3246,14 @@ "tap-mocha-reporter": "^4.0.1", "yaml": "^1.5.0", "yargs": "^13.2.2" + }, + "dependencies": { + "@metarhia/common": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@metarhia/common/-/common-1.5.0.tgz", + "integrity": "sha512-hIiMPmIfDgyTl7Brn7qdAL2EqnsEoXqFUkfeMX5dQUGVjs+HO1NUR3J/KtUWeRuTNiiVQNeNtjB5j7dlZSsAqw==", + "dev": true + } } }, "micromatch": { @@ -3287,7 +3295,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -3326,7 +3334,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -3656,7 +3664,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -3755,7 +3763,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -3978,7 +3986,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "optional": true, @@ -4440,7 +4448,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, diff --git a/package.json b/package.json index 894157d1..07b155eb 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "node": ">=8.0.0" }, "dependencies": { - "@metarhia/common": "^1.4.2" + "@metarhia/common": "^2.1.0" }, "devDependencies": { "@babel/cli": "^7.4.3", diff --git a/test/array.asyncMap.js b/test/array.asyncMap.js index a2deabc3..1dd3f294 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(); } ); }); @@ -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,14 @@ metatests.test('Non-blocking', test => { } ); }); + +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..e3838070 100644 --- a/test/array.each.js +++ b/test/array.each.js @@ -45,29 +45,35 @@ metatests.test('each with empty array', test => { ); }); +metatests.test('successful each with another iterable', test => { + const map = new Map([[1, 'a'], [2, 'b'], [3, 'c']]); + const mapCopy = new Map(); + + metasync.each( + map, + (entry, callback) => + process.nextTick(() => { + mapCopy.set(...entry); + callback(null); + }), + err => { + test.error(err); + test.strictSame([...mapCopy], [...map]); + 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..21e6d638 100644 --- a/test/array.every.js +++ b/test/array.every.js @@ -35,7 +35,7 @@ metatests.test('every with error', test => { }; metasync.every(data, predicate, err => { - test.strictSame(err, everyErr); + test.isError(err, everyErr); test.end(); }); }); @@ -60,14 +60,26 @@ metatests.test('every with two-element arrays', test => ) ); +const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + 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)); - const predicate = (item, callback) => { + 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..f904d38e 100644 --- a/test/array.filter.js +++ b/test/array.filter.js @@ -50,7 +50,7 @@ metatests.test('successful filter', test => { ); }); -metatests.test('filter with empty array', test => { +metatests.test('filter with empty array ', test => { const arr = []; const expectedArr = []; @@ -65,54 +65,44 @@ metatests.test('filter with empty array', test => { ); }); -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('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( + set, + (x, callback) => process.nextTick(() => callback(null, x % 2 === 0)), + (err, res) => { + test.error(err); + test.strictSame([...res], [...expectedSet]); + 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..098a0875 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'; @@ -23,12 +25,11 @@ 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]; const expected = 15; const predicate = (item, callback) => process.nextTick(() => callback(null, item % 3 === 0 && item % 5 === 0)); - metasync.find(data, predicate, (err, result) => { + metasync.find(arr, predicate, (err, result) => { test.error(err, 'must not return an error'); test.strictSame(result, expected, `result should be: ${expected}`); test.end(); @@ -48,9 +49,8 @@ metatests.test('with empty array', test => { }); 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, + arr, (el, callback) => process.nextTick(() => callback(null, el === 20)), (err, result) => { test.error(err); @@ -59,3 +59,16 @@ metatests.test('with array without element which is searching', test => { } ); }); + +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(); + }); +}); diff --git a/test/array.map.js b/test/array.map.js index f059774a..8829c56d 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', test => { const arr = [1, 2, 3]; const expectedArr = [1, 4, 9]; @@ -33,6 +33,21 @@ metatests.test('map with empty array', test => { ); }); +metatests.test('successful map with another iterable', test => { + const set = new Set([1, 2, 3]); + const expectedSet = new Set([1, 4, 9]); + + metasync.map( + set, + (x, callback) => process.nextTick(() => callback(null, x * x)), + (err, res) => { + test.error(err); + test.strictSame([...res], [...expectedSet]); + test.end(); + } + ); +}); + metatests.test('map with error', test => { const arr = [1, 2, 3]; const mapError = new Error('Map error'); @@ -50,9 +65,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..8a4aed3b 100644 --- a/test/array.reduce.js +++ b/test/array.reduce.js @@ -20,6 +20,23 @@ metatests.test('reduce with initial', test => { ); }); +metatests.test('reduce with initial and another iterable', test => { + const set = new Set([1, 2, 3, 4, 5]); + const initial = 10; + const expectedRes = 25; + + metasync.reduce( + set, + (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; @@ -40,28 +57,28 @@ metatests.test('reduce with initial and empty array', test => { metatests.test('reduce without initial and with empty array', 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.reduce( arr, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { - test.strictSame(err, expectedError); + test.isError(err, expectedError); test.strictSame(res, undefined); test.end(); } ); }); -metatests.test('reduce without initial and with single-element array', test => { +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.error(err); test.strictSame(res, 2); test.end(); } @@ -83,12 +100,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 +117,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 +132,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..09932bb0 100644 --- a/test/array.reduceRight.js +++ b/test/array.reduceRight.js @@ -37,17 +37,35 @@ 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 without initial and with empty array', 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, (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { - test.strictSame(err, expectedError); + test.isError(err, expectedError); test.strictSame(res, undefined); test.end(); } @@ -64,7 +82,7 @@ metatests.test( (prev, cur, callback) => process.nextTick(() => callback(null, prev + cur)), (err, res) => { - test.strictSame(err, null); + test.error(err); test.strictSame(res, 2); test.end(); } @@ -87,12 +105,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,11 +122,11 @@ metatests.test('reduceRight with asymetric function', test => { }); metatests.test('reduceRight with error', test => { - const arr = '10120011'; + const str = '10120011'; const reduceError = new Error('Reduce error'); - metasync.reduce( - arr, + metasync.reduceRight( + str, (prev, cur, callback) => process.nextTick(() => { const digit = +cur; @@ -119,7 +137,7 @@ metatests.test('reduceRight 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.series.js b/test/array.series.js index 5171d893..987950be 100644 --- a/test/array.series.js +++ b/test/array.series.js @@ -7,6 +7,7 @@ metatests.test('successful series', 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]; @@ -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..3b44f730 100644 --- a/test/array.some.js +++ b/test/array.some.js @@ -7,6 +7,7 @@ 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); @@ -14,6 +15,17 @@ 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('failing some', test => { const arr = [1, 2, 3]; @@ -32,7 +44,7 @@ metatests.test('erroneous some', test => { 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(); }); 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); } });