Skip to content

Commit 5cb5f7a

Browse files
committed
Split library into multiple files in "lib". Update README.md.
Add tests to check that Promise values are resolved.
1 parent e6fec06 commit 5cb5f7a

File tree

6 files changed

+333
-64
lines changed

6 files changed

+333
-64
lines changed

README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,149 @@
11
# async-iterators
22
`async-iterators` is a library designed to provide iterator functions that run each loop of the iterator asynchronously. This allows you to run CPU intensive loops without blocking the event loop or Promise-based functions in sequenece.
33

4+
The library implements the proposed ECMAScript async iterators functionality by using a custom iterator where `next()` returns a `Promise` of the value. See [Asynchronous Iterators for JavaScript](https://github.com/tc39/proposal-async-iteration).
5+
6+
This library exposes an async iterator function along with a Symbol that can be used to attach the iterator to any object.
7+
8+
In addition to the iterator, there are `map`, `reduce`, and `transform` functions that utilize the async iterator to break up these normally synchronous operations up by using `setImmediate` around each loop iteration.
9+
To prevent blocking the event loop, each `iterator.next()` function is called within a `setImmediate`.
10+
11+
## Dependencies
12+
`async-iterators` has no external dependencies. It is written using ES6 syntax compatible with node 4.x and higher.
13+
14+
## asyncIterator
15+
`async-Iterators` exports `asyncIterator` function and `asyncIteratorSymbol`, which can be used for custom iteration.
16+
17+
### Example
18+
```javascript
19+
const asyncIterator = require('async-iterators');
20+
asyncIterator.addToObjectPrototype(); // = Object.prototype[asyncIteratorSymbol] = asyncIterator;
21+
22+
const transformObj = {
23+
a: 1,
24+
b: 2,
25+
c: 3
26+
};
27+
28+
29+
function transform(obj) {
30+
const iterator = obj[asyncIterator.asyncIteratorSymbol]();
31+
32+
return new Promise((resolve, reject) => {
33+
let sum = 0;
34+
let keys = '';
35+
36+
function loop() {
37+
iterator.next().then(result => {
38+
if (!result.done) {
39+
keys += result.value[0];
40+
sum += result.value[1];
41+
loop();
42+
} else {
43+
resolve({ keys, sum });
44+
}
45+
})
46+
.catch(reject);
47+
}
48+
49+
loop();
50+
});
51+
}
52+
53+
async function doIt() {
54+
return await transform(transformObj);
55+
}
56+
57+
doIt().then(result => console.log({ sum, keys }));
58+
// { keys: 'abc', sum: 6 }
59+
```
60+
61+
### Promises
62+
`asyncIterator.next()` will return the resolved value of Promises that are values in the iterable. Using the same code as above:
63+
64+
```javascript
65+
const promises = [
66+
Promise.resolve(1),
67+
Promise.resolve(2),
68+
Promise.resolve(3)
69+
];
70+
71+
transform(promises).then(result => console.log({ sum, keys }));
72+
// { keys: '012', sum: 6 }
73+
```
74+
75+
So, in the `loop` function, we can call `result.value[1]` and get the value rather than the Promise, since `next()` doesn't resolve until the value of the iterator resolves.
76+
77+
An example use case may be calling out to multiple APIs to collect data, but then needing to process the responses in a specific order as part of a transform. You can have all the promises fire off asyncronously, but the transform would still loop in order as each promise resolves.
78+
79+
## Avoid blocking the event loop with: forEach, map, reduce, transform
80+
The most common use cases for iterating are a simple loop (`forEach`), or doing transforms using `map` or `reduce`. This library provides implementations of these common iteration functions using an `asyncIterator`. These functions support objects and arrays.
81+
82+
These functions use `setImmediate` to fire off each cycle of the loop in order to avoid blocking the event loop. This does add overhead, but it's very useful to avoid blocking the event loop with CPU intensive transformations. They also facilitate using these transforms over arrays or objects of Promises.
83+
84+
These functions automatically use `asyncIterator`, so there's no need to call `asyncIterator.addToObjectPrototype` or add the iterator manually.
85+
86+
### forEach
87+
`forEach` iterates over an array or object but does not return a value.
88+
89+
Signature: `forEach(obj: Iterable, (value, key, obj) => {}): void`
90+
91+
```javascript
92+
const forEach = require('async-iterators').forEach;
93+
94+
const promises = [
95+
Promise.resolve(1),
96+
Promise.resolve(2),
97+
Promise.resolve(3)
98+
];
99+
100+
let sum = 0;
101+
forEach(promises, v => sum += v)
102+
.then(() => console.log(sum));
103+
// 6
104+
```
105+
106+
### map
107+
`map` iterates over an array or object and returns an array with the result of each iteration of the loop. This is the same as `Array.prototype.map` or `lodash.map`.
108+
109+
Signature: `map(obj: Iterable, (value, key, obj) => {}): Array`
110+
111+
```javascript
112+
const map = require('async-iterators').map;
113+
114+
const obj = { a: 1, b: 2, c: 3};
115+
116+
const result = map(obj, (v, k) => v + k)
117+
.then(arr => console.log(arr));
118+
// [ '1a', '2b', '3c' ]
119+
```
120+
121+
### reduce
122+
`reduce` iterates over an array. The `iteratee` function accepts an accumulator value as the first argument and must return a value to be used as the accumulator for the next iteration, or at the end, as the result of the loop.
123+
124+
Signature: `reduce(obj: Iterable, (accumulator, value, key, obj) => {}): any`
125+
126+
```javascript
127+
const reduce = require('async-iterators').reduce;
128+
129+
const obj = { a: 1, b: 2, c: 3 };
130+
131+
const result = reduce(obj, (accum, v, k) => accum + v + k, '').
132+
then(result => console.log(`result: ${result}`));
133+
// '1a2b3c'
134+
```
135+
136+
### transform
137+
`transform` is based on `reduce`, except that the initial accumulator defaults to an empty object, and iteratee function doesn't have to return the object in each loop. The iterator is mutated during the loop and then returned.
138+
139+
Signature: `transform(obj: Iterable, (accumulator, value, key, obj) => {}): object`
140+
141+
```javascript
142+
const transform = require('async-iterators').transform;
143+
144+
const obj = { a: 1, b: 2, c: 3 };
145+
146+
const result = transform(obj, (accum, v, k) => v > 1 ? accum[k.toUpperCase()] = v * 2 : undefined).
147+
then(result => console.log('result:', result));
148+
// result: { B: 4, C: 6 }
149+
```

index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const funcs = require('./lib/iteratorFunctions');
4+
const asyncIterator = require('./lib/asyncIterator');
5+
6+
module.exports = Object.assign({}, funcs, asyncIterator);

lib/asyncIterator.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
const asyncIteratorSymbol = Symbol('asyncIterator');
4+
5+
/**
6+
* @typedef {Function<Promise<{value: Array, done: boolean}>>} NextFunction
7+
*/
8+
9+
/**
10+
* Returns an asyncIterator implementation
11+
*
12+
* @returns {{ next: NextFunction }}
13+
*
14+
* @example
15+
* var a = { prop1: true, prop2: { prop3: 'foo' } };
16+
* a[Symbol.iterator] = objectIterator;
17+
* for (let v of a) { console.log(v); }
18+
*/
19+
function asyncIterator() {
20+
let iter;
21+
if (typeof this.entries === 'function') {
22+
iter = this.entries();
23+
return {
24+
next: () => {
25+
const nextValue = iter.next();
26+
if (Array.isArray(nextValue.value)) {
27+
return Promise.all(nextValue.value)
28+
.then(result => ({
29+
value: result,
30+
done: nextValue.done
31+
}));
32+
}
33+
return Promise.resolve(nextValue);
34+
}
35+
}
36+
} else if (typeof this === 'object') {
37+
iter = Object.getOwnPropertyNames(this)[Symbol.iterator]();
38+
return {
39+
next: () => {
40+
const key = iter.next();
41+
const value = key.value ? this[key.value] : undefined;
42+
// Return a value that's compatible with Array.prototype.entries()
43+
return Promise.all([key.value, value, key.done])
44+
.then(result => ({
45+
value: result[0] ? [result[0], result[1]] : undefined,
46+
done: result[2]
47+
}));
48+
}
49+
}
50+
} else {
51+
return singleValueIterator(this);
52+
}
53+
}
54+
55+
function singleValueIterator(value) {
56+
let done = false;
57+
return {
58+
next: () => {
59+
let ret = {
60+
value: [void 0, void 0],
61+
done
62+
};
63+
64+
if (!done) {
65+
// Return a value that's compatible with Array.prototype.entries()
66+
ret.value = [void 0, value],
67+
done = true;
68+
}
69+
70+
return Promise.resolve(ret);
71+
}
72+
}
73+
}
74+
75+
function addToObjectPrototype() {
76+
Object.prototype[asyncIteratorSymbol] = asyncIterator;
77+
}
78+
79+
module.exports = {
80+
addToObjectPrototype,
81+
asyncIterator,
82+
asyncIteratorSymbol,
83+
singleValueIterator
84+
};

lib/index.js renamed to lib/iteratorFunctions.js

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,9 @@
11
"use strict";
22

3-
/**
4-
* Returns an iterator implementation
5-
*
6-
* @example
7-
* var a = { prop1: true, prop2: { prop3: 'foo' } };
8-
* a[Symbol.iterator] = objectIterator;
9-
* for (let v of a) { console.log(v); }
10-
*/
11-
function asyncIterator() {
12-
let iter;
13-
if (typeof this.entries === 'function') {
14-
iter = this.entries();
15-
return {
16-
next: () => {
17-
return Promise.resolve(iter.next());
18-
}
19-
}
20-
} else if (typeof this === 'object') {
21-
iter = Object.getOwnPropertyNames(this)[Symbol.iterator]();
22-
return {
23-
next: () => {
24-
const key = iter.next();
25-
const value = this[key.value];
26-
// Return a value that's compatible with Array.prototype.entries()
27-
return Promise.resolve({
28-
value: [key.value, value],
29-
done: key.done
30-
});
31-
}
32-
}
33-
} else {
34-
return singleValueIterator(this);
35-
}
36-
}
37-
38-
function singleValueIterator(value) {
39-
let done = false;
40-
return {
41-
next: () => {
42-
let ret = {
43-
value: [void 0, void 0],
44-
done
45-
};
46-
47-
if (!done) {
48-
// Return a value that's compatible with Array.prototype.entries()
49-
ret.value = [void 0, value],
50-
done = true;
51-
}
52-
53-
return Promise.resolve(ret);
54-
}
55-
}
56-
}
3+
const a = require('./asyncIterator');
574

585
function iterate(obj, fn, initAccum) {
59-
const iter = asyncIterator.call(obj);
6+
const iter = (obj[a.asyncIteratorSymbol] || a.asyncIterator).call(obj);
607

618
return new Promise((resolve, reject) => {
629
(function next(prevResult) {
@@ -124,7 +71,6 @@ function transform(obj, fn, initAccum) {
12471
}
12572

12673
module.exports = {
127-
asyncIterator,
12874
forEach,
12975
map,
13076
reduce,

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "async-iterators",
3-
"version": "1.0.0-dev",
3+
"version": "1.0.0",
44
"description": "Iterator functions that run each cycle of the loop asynchronously",
55
"main": "index.js",
66
"engines": {
7-
"node": ">4.0.0"
7+
"node": ">=4.0.0"
88
},
99
"scripts": {
1010
"test": "mocha"

0 commit comments

Comments
 (0)