Skip to content

Commit

Permalink
Add support for iterable objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Yehor Khilchenko authored and o-rumiantsev committed May 3, 2019
1 parent d619834 commit a65fd11
Show file tree
Hide file tree
Showing 15 changed files with 1,064 additions and 672 deletions.
404 changes: 93 additions & 311 deletions lib/array.js

Large diffs are not rendered by default.

32 changes: 25 additions & 7 deletions lib/async-iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ class AsyncIterator {
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)) {
Expand Down Expand Up @@ -195,6 +199,10 @@ class AsyncIterator {
enumerate() {
return new EnumerateIterator(this);
}

reverse() {
return new ReverseIterator(this.base);
}
}

class MapIterator extends AsyncIterator {
Expand All @@ -213,6 +221,16 @@ class MapIterator extends AsyncIterator {
}
}

class ReverseIterator extends AsyncIterator {
constructor(base) {
const newBase = [];
for (const value of base) {
newBase.unshift(value);
}
super(newBase);
}
}

class FilterIterator extends AsyncIterator {
constructor(base, predicate, thisArg) {
super(base);
Expand Down Expand Up @@ -403,29 +421,29 @@ 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++;
return this.base.next();
if (this.iterMax > this.iterCount) {
this.iterCount++;
return await 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();
const loopTime = Date.now() - this.begin;

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();
}
Expand Down
3 changes: 1 addition & 2 deletions lib/control.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use strict';

const common = require('@metarhia/common');

const { each } = require('./array');
const { each } = require('./array.js');

// Executes all asynchronous functions and pass first result to callback
// fns - <Function[]>, callback-last / err-first
Expand Down
94 changes: 83 additions & 11 deletions test/array.asyncMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,62 @@
const metasync = require('..');
const metatests = require('metatests');

metatests.test('succesfull map', test => {
test.plan(2);

metatests.test('successful map / Array', test => {
const arr = [1, 2, 3];
const expectedArr = [2, 4, 6];
const expected = [2, 4, 6];

metasync.asyncMap(
arr,
item => item * 2,
(x, callback) => process.nextTick(() => callback(null, x * 2)),
(err, newArr) => {
test.error(err);
test.strictSame(newArr, expectedArr);
test.strictSame(newArr, expected);
test.end();
}
);
});

metatests.test('successful map / Set', test => {
const set = new Set([1, 2, 3]);
const expected = new Set([2, 4, 6]);

metasync.asyncMap(
set,
(x, callback) => process.nextTick(() => callback(null, x * 2)),
(err, newSet) => {
test.error(err);
test.strictSame([...newSet], [...expected]);
test.end();
}
);
});

metatests.test('successful map / Map', test => {
const map = new Map([[1, 'a'], [2, 'b'], [3, 'c']]);
const expected = new Map([['a', 1], ['b', 2], ['c', 3]]);

metasync.asyncMap(
map,
(x, callback) => process.nextTick(() => callback(null, x.reverse())),
(err, res) => {
test.error(err);
test.strictSame([...res], [...expected]);
test.end();
}
);
});

metatests.test('successful map / String', test => {
const str = 'abcdefgh';
const expected = 'A,B,C,D,E,F,G,H';

metasync.asyncMap(
str,
(x, callback) => process.nextTick(() => callback(null, x.toUpperCase())),
(err, res) => {
test.error(err);
test.strictSame([...res], [...expected]);
test.end();
}
);
});
Expand All @@ -24,25 +68,26 @@ 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;
const EXPECTED_PERCENT = 0.5;
const EXPECTED_DEVIATION = 0.2;

const arr = new Array(ARRAY_SIZE).fill(1);

const timer = setInterval(() => doSmth(TIMER_TIME), 1);

const begin = Date.now();

metasync.asyncMap(
arr,
() => doSmth(ITEM_TIME),
(x, callback) => {
doSmth(ITEM_TIME);
callback();
},
{ percent: EXPECTED_PERCENT },
() => {
clearInterval(timer);

const mapTime = ITEM_TIME * ARRAY_SIZE;
const allTime = Date.now() - begin;
const actualPercent = mapTime / allTime;
Expand All @@ -52,3 +97,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' };
const expectedError = new TypeError('"items" argument is not iterable');

metasync.asyncMap(obj, test.mustNotCall(), (err, res) => {
test.isError(err, expectedError);
test.assertNot(res);
test.end();
});
});
121 changes: 89 additions & 32 deletions test/array.each.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,128 @@
const metasync = require('..');
const metatests = require('metatests');

metatests.test('successful each', test => {
metatests.test('successful each / Array', test => {
const arr = [1, 2, 3, 4];

const elementsSet = new Set();
const expectedElementsSet = new Set(arr);
const arrCopy = [];

metasync.each(
arr,
(el, callback) =>
(item, callback) =>
process.nextTick(() => {
elementsSet.add(el);
arrCopy.push(item);
callback(null);
}),
err => {
test.error(err);
test.strictSame(elementsSet, expectedElementsSet);
test.strictSame(arrCopy, arr);
test.end();
}
);
});

metatests.test('each with empty array', test => {
const arr = [];
metatests.test('successful each / Set', test => {
const set = new Set([1, 2, 3, 4, 5]);
const setCopy = new Set();

const elementsSet = new Set();
const expectedElementsSet = new Set(arr);
metasync.each(
set,
(item, callback) =>
process.nextTick(() => {
setCopy.add(item);
callback(null);
}),
err => {
test.error(err);
test.strictSame([...setCopy], [...set]);
test.end();
}
);
});

metatests.test('successful each / Map', 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(() => {
mapCopy.set(...entry);
callback(null);
}),
err => {
test.error(err);
test.strictSame([...mapCopy], [...map]);
test.end();
}
);
});

metatests.test('successful each / String', test => {
const str = 'aaabcdeefff';
let strCopy = '';

metasync.each(
str,
(item, callback) =>
process.nextTick(() => {
elementsSet.add(el);
strCopy += item;
callback(null);
}),
err => {
test.error(err);
test.strictSame(elementsSet, expectedElementsSet);
test.strictSame(strCopy, str);
test.end();
}
);
});

metatests.test('each with empty / Array', test => {
const arr = [];

metasync.each(arr, test.mustNotCall(), err => {
test.error(err);
test.end();
});
});

metatests.test('each with empty / Set', test => {
const set = new Set();

metasync.each(set, test.mustNotCall(), err => {
test.error(err);
test.end();
});
});

metatests.test('each with empty / Map', test => {
const map = new Map();

metasync.each(map, test.mustNotCall(), err => {
test.error(err);
test.end();
});
});

metatests.test('each with empty / String', test => {
const str = '';

metasync.each(str, 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');
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);
}
}),
(x, callback) =>
process.nextTick(() => callback(x === 3 ? eachError : null)),
err => {
test.strictSame(err, eachError);
test.strictSame(elementsSet.size, expectedElementsCount);
test.isError(err, eachError);
test.end();
}
);
Expand Down

0 comments on commit a65fd11

Please sign in to comment.