-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mapping over Objects #1177
Mapping over Objects #1177
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); |
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); | ||
}); | ||
} |
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); |
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]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, I'm experimenting with this way of verifying concurrency. It should be more reliable than asserting a concurrency after some timeout value. Could help with some of our flaky tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, let me know if you want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'd have to refactor other tests to be like this style, tracking concurrency and verifying things finish in a certain order, but it might be worth it 😓 |
||
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(); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth testing with an array as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would the expected behavior be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{0: <val>, 1: <val>, 2: <val>}
I would also be fine with leaving this behaviour implied