Skip to content

Commit 4fe06de

Browse files
ivan-tymoshenkobelochub
authored andcommitted
Implement asyncMap function
Closes: #391 PR-URL: #387
1 parent 2fcc5d0 commit 4fe06de

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

lib/array.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,69 @@ const map = (
3838
}
3939
};
4040

41+
const DEFAULT_OPTIONS = { min: 5, percent: 0.7 };
42+
43+
// Non-blocking synchronous map
44+
// items - <Array>, incoming dataset
45+
// fn - <Function>
46+
// item - <any>
47+
// index - <number>
48+
// options - <Object>, map params { min, percent }
49+
// min - <number>, min number of items in one next call
50+
// percent - <number>, ratio of map time to all time
51+
// done - <Function>, call on done
52+
// err - <Error> | <null>
53+
// result - <Array>
54+
const asyncMap = (items, fn, options = {}, done) => {
55+
if (typeof options === 'function') {
56+
done = options;
57+
options = DEFAULT_OPTIONS;
58+
}
59+
60+
if (!items.length) {
61+
if (done) done(null, []);
62+
return;
63+
}
64+
65+
const min = options.min || DEFAULT_OPTIONS.min;
66+
const percent = options.percent || DEFAULT_OPTIONS.percent;
67+
68+
let begin;
69+
let sum = 0;
70+
let count = 0;
71+
72+
const result = done ? new Array(items.length) : null;
73+
const ratio = percent / (1 - percent);
74+
75+
const countNumber = () => {
76+
const loopTime = Date.now() - begin;
77+
const itemTime = sum / count;
78+
const necessaryNumber = ratio * loopTime / itemTime;
79+
return Math.max(necessaryNumber, min);
80+
};
81+
82+
const next = () => {
83+
const itemsNumber = count ? countNumber() : min;
84+
const iterMax = Math.min(items.length, itemsNumber + count);
85+
86+
begin = Date.now();
87+
for (; count < iterMax; count++) {
88+
const itemResult = fn(items[count], count);
89+
if (done) result[count] = itemResult;
90+
}
91+
sum += Date.now() - begin;
92+
93+
if (count < items.length) {
94+
begin = Date.now();
95+
setTimeout(next, 0);
96+
} else if (done) {
97+
done(null, result);
98+
}
99+
};
100+
101+
next();
102+
};
103+
41104
const filter = (
42105
// Asynchrous filter (iterate parallel)
43106
items, // array, incoming
@@ -303,4 +366,5 @@ module.exports = {
303366
find,
304367
every,
305368
some,
369+
asyncMap,
306370
};

test/array.asyncMap.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
const metasync = require('..');
4+
const metatests = require('metatests');
5+
6+
metatests.test('succesfull map', test => {
7+
test.plan(2);
8+
9+
const arr = [1, 2, 3];
10+
const expectedArr = [2, 4, 6];
11+
12+
metasync.asyncMap(arr, item => item * 2, (err, newArr) => {
13+
test.error(err);
14+
test.strictSame(newArr, expectedArr);
15+
});
16+
});
17+
18+
const doSmth = time => {
19+
const begin = Date.now();
20+
while (Date.now() - begin < time);
21+
};
22+
23+
metatests.test('Non-blocking', test => {
24+
const ARRAY_SIZE = 1000;
25+
const MIN_IO_CALLS = 90;
26+
const MAX_IO_CALLS = 110;
27+
28+
let ioCallsCount = 0;
29+
const arr = new Array(ARRAY_SIZE).fill(1);
30+
31+
const timer = setInterval(() => {
32+
doSmth(9);
33+
ioCallsCount++;
34+
}, 1);
35+
36+
metasync.asyncMap(arr, () => doSmth(1),
37+
{ percent: 0.5 }, () => {
38+
clearInterval(timer);
39+
test.assert(ioCallsCount >= MIN_IO_CALLS);
40+
test.assert(ioCallsCount <= MAX_IO_CALLS);
41+
test.end();
42+
});
43+
});
44+

0 commit comments

Comments
 (0)