summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraeme Yeates <yeatesgraeme@gmail.com>2016-06-05 18:10:12 -0400
committerGraeme Yeates <yeatesgraeme@gmail.com>2016-06-05 18:10:12 -0400
commit872cb9133882302627e989432f3aa7a028ccfa3f (patch)
tree01e102d023ec5ecf5e4efed3c560658d660e038a
parentb49787267c8c8d606debf0fa4f4be6ef793270f5 (diff)
parent611a4424b4490592fd037279f65e22630e66ffd4 (diff)
downloadasync-872cb9133882302627e989432f3aa7a028ccfa3f.tar.gz
Merge pull request #1177 from caolan/map-objects
Mapping over Objects
-rw-r--r--lib/index.js9
-rw-r--r--lib/internal/map.js9
-rw-r--r--lib/map.js6
-rw-r--r--lib/mapValues.js46
-rw-r--r--lib/mapValuesLimit.js33
-rw-r--r--lib/mapValuesSeries.js21
-rw-r--r--mocha_test/map.js18
-rw-r--r--mocha_test/mapValues.js92
8 files changed, 218 insertions, 16 deletions
diff --git a/lib/index.js b/lib/index.js
index 1180dcf..091f657 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -43,6 +43,9 @@ import log from './log';
import map from './map';
import mapLimit from './mapLimit';
import mapSeries from './mapSeries';
+import mapValues from './mapValues';
+import mapValuesLimit from './mapValuesLimit';
+import mapValuesSeries from './mapValuesSeries';
import memoize from './memoize';
import nextTick from './nextTick';
import parallel from './parallel';
@@ -115,6 +118,9 @@ export default {
map: map,
mapLimit: mapLimit,
mapSeries: mapSeries,
+ mapValues: mapValues,
+ mapValuesLimit: mapValuesLimit,
+ mapValuesSeries: mapValuesSeries,
memoize: memoize,
nextTick: nextTick,
parallel: parallel,
@@ -205,6 +211,9 @@ export {
map as map,
mapLimit as mapLimit,
mapSeries as mapSeries,
+ mapValues as mapValues,
+ mapValuesLimit as mapValuesLimit,
+ mapValuesSeries as mapValuesSeries,
memoize as memoize,
nextTick as nextTick,
parallel as parallel,
diff --git a/lib/internal/map.js b/lib/internal/map.js
index 45c3eae..2c2a750 100644
--- a/lib/internal/map.js
+++ b/lib/internal/map.js
@@ -1,13 +1,14 @@
-import isArrayLike from 'lodash/isArrayLike';
-import getIterator from './getIterator';
import noop from 'lodash/noop';
import once from './once';
export default function _asyncMap(eachfn, arr, iteratee, callback) {
callback = once(callback || noop);
arr = arr || [];
- var results = isArrayLike(arr) || getIterator(arr) ? [] : {};
- eachfn(arr, function (value, index, callback) {
+ var results = [];
+ var counter = 0;
+
+ eachfn(arr, function (value, _, callback) {
+ var index = counter++;
iteratee(value, function (err, v) {
results[index] = v;
callback(err);
diff --git a/lib/map.js b/lib/map.js
index 892456b..19642c8 100644
--- a/lib/map.js
+++ b/lib/map.js
@@ -14,6 +14,10 @@ import doLimit from './internal/doLimit';
* in order. However, the results array will be in the same order as the
* original `coll`.
*
+ * If `map` is passed an Object, the results will be an Array. The results
+ * will roughly be in the order of the original Objects' keys (but this can
+ * vary across JavaScript engines)
+ *
* @name map
* @static
* @memberOf async
@@ -24,7 +28,7 @@ import doLimit from './internal/doLimit';
* once it has completed with an error (which can be `null`) and a
* transformed item. Invoked with (item, callback).
* @param {Function} [callback] - A callback which is called when all `iteratee`
- * functions have finished, or an error occurs. Results is an array of the
+ * functions have finished, or an error occurs. Results is an Array of the
* transformed items from the `coll`. Invoked with (err, results).
* @example
*
diff --git a/lib/mapValues.js b/lib/mapValues.js
new file mode 100644
index 0000000..83ffc43
--- /dev/null
+++ b/lib/mapValues.js
@@ -0,0 +1,46 @@
+import mapValuesLimit from './mapValuesLimit';
+import doLimit from './internal/doLimit';
+
+
+/**
+ * A relative of `map`, designed for use with objects.
+ *
+ * Produces a new Object by mapping each value of `obj` through the `iteratee`
+ * function. The `iteratee` is called each `value` and `key` from `obj` and a
+ * callback for when it has finished processing. Each of these callbacks takes
+ * two arguments: an `error`, and the transformed item from `obj`. If `iteratee`
+ * passes an error to its callback, the main `callback` (for the `mapValues`
+ * function) is immediately called with the error.
+ *
+ * Note, the order of the keys in the result is not guaranteed. The keys will
+ * be roughly in the order they complete, (but this is very engine-specific)
+ *
+ * @name mapValues
+ * @static
+ * @memberOf async
+ * @category Collection
+ * @param {Object} obj - A collection to iterate over.
+ * @param {Function} iteratee - A function to apply to each value and key in
+ * `coll`. The iteratee is passed a `callback(err, transformed)` which must be
+ * called once it has completed with an error (which can be `null`) and a
+ * transformed value. Invoked with (value, key, callback).
+ * @param {Function} [callback] - A callback which is called when all `iteratee`
+ * functions have finished, or an error occurs. Results is an array of the
+ * transformed items from the `obj`. Invoked with (err, result).
+ * @example
+ *
+ * async.mapValues({
+ * f1: 'file1',
+ * f2: 'file2',
+ * f3: 'file3'
+ * }, fs.stat, function(err, result) {
+ * // results is now a map of stats for each file, e.g.
+ * // {
+ * // f1: [stats for file1],
+ * // f2: [stats for file2],
+ * // f3: [stats for file3]
+ * // }
+ * });
+ */
+
+export default doLimit(mapValuesLimit, Infinity);
diff --git a/lib/mapValuesLimit.js b/lib/mapValuesLimit.js
new file mode 100644
index 0000000..762b871
--- /dev/null
+++ b/lib/mapValuesLimit.js
@@ -0,0 +1,33 @@
+import eachOfLimit from './eachOfLimit';
+
+/**
+ * The same as `mapValues` but runs a maximum of `limit` async operations at a
+ * time.
+ *
+ * @name mapValuesLimit
+ * @static
+ * @memberOf async
+ * @see async.mapValues
+ * @category Collection
+ * @param {Object} obj - A collection to iterate over.
+ * @param {number} limit - The maximum number of async operations at a time.
+ * @param {Function} iteratee - A function to apply to each value in `obj`.
+ * The iteratee is passed a `callback(err, transformed)` which must be called
+ * once it has completed with an error (which can be `null`) and a
+ * transformed value. Invoked with (value, key, callback).
+ * @param {Function} [callback] - A callback which is called when all `iteratee`
+ * functions have finished, or an error occurs. Result is an object of the
+ * transformed values from the `obj`. Invoked with (err, result).
+ */
+export default function mapValuesLimit(obj, limit, iteratee, callback) {
+ var newObj = {};
+ eachOfLimit(obj, limit, function(val, key, next) {
+ iteratee(val, key, function (err, result) {
+ if (err) return next(err);
+ newObj[key] = result;
+ next();
+ });
+ }, function (err) {
+ callback(err, newObj);
+ });
+}
diff --git a/lib/mapValuesSeries.js b/lib/mapValuesSeries.js
new file mode 100644
index 0000000..163d474
--- /dev/null
+++ b/lib/mapValuesSeries.js
@@ -0,0 +1,21 @@
+import mapValuesLimit from './mapValuesLimit';
+import doLimit from './internal/doLimit';
+
+/**
+ * The same as `mapValues` but runs only a single async operation at a time.
+ *
+ * @name mapValuesSeries
+ * @static
+ * @memberOf async
+ * @see async.mapValues
+ * @category Collection
+ * @param {Object} obj - A collection to iterate over.
+ * @param {Function} iteratee - A function to apply to each value in `obj`.
+ * The iteratee is passed a `callback(err, transformed)` which must be called
+ * once it has completed with an error (which can be `null`) and a
+ * transformed value. Invoked with (value, key, callback).
+ * @param {Function} [callback] - A callback which is called when all `iteratee`
+ * functions have finished, or an error occurs. Result is an object of the
+ * transformed values from the `obj`. Invoked with (err, result).
+ */
+export default doLimit(mapValuesLimit, 1);
diff --git a/mocha_test/map.js b/mocha_test/map.js
index a537427..e7d4fa3 100644
--- a/mocha_test/map.js
+++ b/mocha_test/map.js
@@ -122,12 +122,10 @@ describe("map", function() {
callback(null, val * 2);
}, function(err, result) {
if (err) throw err;
- expect(Object.prototype.toString.call(result)).to.equal('[object Object]');
- expect(result).to.eql({
- a: 2,
- b: 4,
- c: 6
- });
+ expect(Object.prototype.toString.call(result)).to.equal('[object Array]');
+ expect(result).to.contain(2);
+ expect(result).to.contain(4);
+ expect(result).to.contain(6);
done();
});
});
@@ -170,11 +168,9 @@ describe("map", function() {
callback(null, val * 2);
}, function(err, result) {
if (err) throw err;
- expect(result).to.eql({
- a: 2,
- b: 4,
- c: 6
- });
+ expect(result).to.contain(2);
+ expect(result).to.contain(4);
+ expect(result).to.contain(6);
done();
});
});
diff --git a/mocha_test/mapValues.js b/mocha_test/mapValues.js
new file mode 100644
index 0000000..44eb27d
--- /dev/null
+++ b/mocha_test/mapValues.js
@@ -0,0 +1,92 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+var assert = require('assert');
+
+describe('mapValues', function () {
+ var obj = {a: 1, b: 2, c: 3};
+
+ context('mapValuesLimit', function () {
+ it('basics', function (done) {
+ var running = 0;
+ var concurrency = {
+ a: 2,
+ b: 2,
+ c: 1
+ };
+ async.mapValuesLimit(obj, 2, function (val, key, next) {
+ running++;
+ async.setImmediate(function () {
+ expect(running).to.equal(concurrency[key]);
+ running--;
+ next(null, key + val);
+ });
+ }, function (err, result) {
+ expect(running).to.equal(0);
+ expect(err).to.eql(null);
+ expect(result).to.eql({a: 'a1', b: 'b2', c: 'c3'});
+ done();
+ });
+ });
+
+ it('error', function (done) {
+ async.mapValuesLimit(obj, 1, function(val, key, next) {
+ if (key === 'b') {
+ return next(new Error("fail"));
+ }
+ next(null, val);
+ }, function (err, result) {
+ expect(err).to.not.eql(null);
+ expect(result).to.eql({a: 1});
+ done();
+ });
+ });
+ });
+
+ context('mapValues', function () {
+ it('basics', function (done) {
+ var running = 0;
+ var concurrency = {
+ a: 3,
+ b: 2,
+ c: 1
+ };
+ async.mapValues(obj, function (val, key, next) {
+ running++;
+ async.setImmediate(function () {
+ expect(running).to.equal(concurrency[key]);
+ running--;
+ next(null, key + val);
+ });
+ }, function (err, result) {
+ expect(running).to.equal(0);
+ expect(err).to.eql(null);
+ expect(result).to.eql({a: 'a1', b: 'b2', c: 'c3'});
+ done();
+ });
+ });
+ });
+
+ context('mapValuesSeries', function () {
+ it('basics', function (done) {
+ var running = 0;
+ var concurrency = {
+ a: 1,
+ b: 1,
+ c: 1
+ };
+ async.mapValuesSeries(obj, function (val, key, next) {
+ running++;
+ async.setImmediate(function () {
+ expect(running).to.equal(concurrency[key]);
+ running--;
+ next(null, key + val);
+ });
+ }, function (err, result) {
+ expect(running).to.equal(0);
+ expect(err).to.eql(null);
+ expect(result).to.eql({a: 'a1', b: 'b2', c: 'c3'});
+ done();
+ });
+ });
+ });
+});