Skip to content

Commit

Permalink
Merge pull request #705 from aearly/for-each-of
Browse files Browse the repository at this point in the history
Completing forEachOf.  Also closes #168 and #321
  • Loading branch information
aearly committed May 19, 2015
2 parents de3a160 + ae5d25f commit 57cf750
Show file tree
Hide file tree
Showing 3 changed files with 390 additions and 14 deletions.
67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ Usage:
* [`each`](#each)
* [`eachSeries`](#eachSeries)
* [`eachLimit`](#eachLimit)
* [`forEachOf`](#forEachOf)
* [`forEachOfSeries`](#forEachOfSeries)
* [`forEachOfLimit`](#forEachOfLimit)
* [`map`](#map)
* [`mapSeries`](#mapSeries)
* [`mapLimit`](#mapLimit)
Expand Down Expand Up @@ -191,7 +194,8 @@ __Arguments__
* `iterator(item, callback)` - A function to apply to each item in `arr`.
The iterator is passed a `callback(err)` which must be called once it has
completed. If no error has occurred, the `callback` should be run without
arguments or with an explicit `null` argument.
arguments or with an explicit `null` argument. The array index is not passed
to the iterator. If you need the index, use [`forEachOf`](#forEachOf).
* `callback(err)` - A callback which is called when all `iterator` functions
have finished, or an error occurs.

Expand Down Expand Up @@ -280,6 +284,67 @@ async.eachLimit(documents, 20, requestApi, function(err){
});
```

---------------------------------------

<a name="forEachOf" />
<a name="eachOf" />

### forEachOf(obj, iterator, callback)

Like `each`, except that it iterates over objects, and passes the key as the second argument to the iterator.

__Arguments__

* `obj` - An object or array to iterate over.
* `iterator(item, key, callback)` - A function to apply to each item in `obj`.
The `key` is the item's key, or index in the case of an array. The iterator is
passed a `callback(err)` which must be called once it has completed. If no
error has occurred, the callback should be run without arguments or with an
explicit `null` argument.
* `callback(err)` - A callback which is called when all `iterator` functions have finished, or an error occurs.

__Example__

```js
var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
var configs = {};

async.forEachOf(obj, function (value, key, callback) {
fs.readFile(__dirname + value, "utf8", function (err, data) {
if (err) return callback(err);
try {
configs[key] = JSON.parse(data);
} catch (e) {
return callback(e);
}
callback();
})
}, function (err) {
if (err) console.error(err.message);
// configs is now a map of JSON data
doSomethingWith(configs);
})
```

---------------------------------------

<a name="forEachOfSeries" />
<a name="eachOfSeries" />

### forEachOfSeries(obj, iterator, callback)

Like [`forEachOf`](#forEachOf), except only one `iterator` is run at a time. The order of execution is not guaranteed for objects, but it will be guaranteed for arrays.

---------------------------------------

<a name="forEachOfLimit" />
<a name="eachOfLimit" />

### forEachOfLimit(obj, limit, iterator, callback)

Like [`forEachOf`](#forEachOf), except the number of `iterator`s running at a given time is controlled by `limit`.


---------------------------------------

<a name="map" />
Expand Down
118 changes: 118 additions & 0 deletions lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@
return memo;
};

var _forEachOf = function (object, iterator) {
for (key in object) {
if (object.hasOwnProperty(key)) {
iterator(object[key], key);
}
}
};

var _keys = function (obj) {
if (Object.keys) {
return Object.keys(obj);
Expand Down Expand Up @@ -163,6 +171,7 @@
};
async.forEachSeries = async.eachSeries;


async.eachLimit = function (arr, limit, iterator, callback) {
var fn = _eachLimit(limit);
fn.apply(null, [arr, iterator, callback]);
Expand Down Expand Up @@ -210,6 +219,115 @@
};



async.forEachOf = async.eachOf = function (object, iterator, callback) {
callback = callback || function () {};
var size = object.length || _keys(object).length;
var completed = 0
if (!size) {
return callback();
}
_forEachOf(object, function (value, key) {
iterator(object[key], key, function (err) {
if (err) {
callback(err);
callback = function () {};
} else {
completed += 1;
if (completed === size) {
callback(null);
}
}
});
});
};

async.forEachOfSeries = async.eachOfSeries = function (obj, iterator, callback) {
callback = callback || function () {};
var keys = _keys(obj);
var size = keys.length;
if (!size) {
return callback();
}
var completed = 0;
var iterate = function () {
var sync = true;
var key = keys[completed];
iterator(obj[key], key, function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= size) {
callback(null);
}
else {
if (sync) {
async.nextTick(iterate);
}
else {
iterate();
}
}
}
});
sync = false;
};
iterate();
};



async.forEachOfLimit = async.eachOfLimit = function (obj, limit, iterator, callback) {
_forEachOfLimit(limit)(obj, iterator, callback);
};

var _forEachOfLimit = function (limit) {

return function (obj, iterator, callback) {
callback = callback || function () {};
var keys = _keys(obj);
var size = keys.length;
if (!size || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;

(function replenish () {
if (completed >= size) {
return callback();
}

while (running < limit && started < size) {
started += 1;
running += 1;
var key = keys[started - 1];
iterator(obj[key], key, function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
running -= 1;
if (completed >= size) {
callback();
}
else {
replenish();
}
}
});
}
})();
};
};


var doParallel = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
Expand Down
Loading

0 comments on commit 57cf750

Please sign in to comment.