Skip to content

Commit

Permalink
Merge pull request caolan#1177 from caolan/map-objects
Browse files Browse the repository at this point in the history
Mapping over Objects
  • Loading branch information
megawac committed Jun 5, 2016
2 parents 25553d3 + 611a442 commit 3e43f0d
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 16 deletions.
9 changes: 9 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -115,6 +118,9 @@ export default {
map: map,
mapLimit: mapLimit,
mapSeries: mapSeries,
mapValues: mapValues,
mapValuesLimit: mapValuesLimit,
mapValuesSeries: mapValuesSeries,
memoize: memoize,
nextTick: nextTick,
parallel: parallel,
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/map.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
6 changes: 5 additions & 1 deletion lib/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
*
Expand Down
46 changes: 46 additions & 0 deletions lib/mapValues.js
Original file line number Diff line number Diff line change
@@ -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);
33 changes: 33 additions & 0 deletions lib/mapValuesLimit.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
21 changes: 21 additions & 0 deletions lib/mapValuesSeries.js
Original file line number Diff line number Diff line change
@@ -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);
18 changes: 7 additions & 11 deletions mocha_test/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Expand Down Expand Up @@ -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();
});
});
Expand Down
92 changes: 92 additions & 0 deletions mocha_test/mapValues.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
});

0 comments on commit 3e43f0d

Please sign in to comment.