Skip to content
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

iteratorToArray Support #2894

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions modules/isIterable.js
@@ -0,0 +1,7 @@
function isIterable(obj) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you intended isIterable to be a public function (i.e., mixed into the _ object), then you should list it in modules/index.js. There should also be a comment before the function to explain its purpose. Take a look at a few other public modules to get a taste of how we write these comments.

If you intended isIterable to be only an internal function instead, then you should start the name of the module with a leading underscore, i.e., modules/_isIterable.js. In this case it would still be desirable to have a comment that explains the purpose of the function, but I'll readily admit that there are currently more internal functions where this is missing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you forgot export default.

// checks for null and undefined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unnecessary comment.

if (obj == null) {
return false;
}
return typeof obj[Symbol.iterator] === 'function';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't safely assume that Symbol or Symbol.iterator is defined, because that will (needlessly!) break compatibility with older environments that only know ES3. Instead, we would use feature detection and then create an internal getIterator function that simply returns undefined in environments where these objects don't exist. You can see an example of this practice in modules/keys.js, which uses Object.keys but only if it exists.

A similar, but more subtle, consideration applies to typeof x === 'function'. In some environments, this expression doesn't work as intended. This is why you should use _.isFunction instead whenever possible. isFunction(x) can also be minified much more compactly than typeof x === 'function'.

So this line should have read like this:

Suggested change
return typeof obj[Symbol.iterator] === 'function';
return isFunction(getIterator(obj));

Comment on lines +3 to +6
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given my previous comment, the entire body of this function would actually fit into a single expression:

Suggested change
if (obj == null) {
return false;
}
return typeof obj[Symbol.iterator] === 'function';
return obj != null && isFunction(getIterator(obj));

Though getIterator should probably check against null and undefined anyway, so you could actually leave out the null check entirely.

}
9 changes: 9 additions & 0 deletions modules/iteratorToArray.js
@@ -0,0 +1,9 @@
export default function iteratorToArray(iterator) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like with isIterable, you should either add a descriptive comment and list this function in modules/index.js if you intended this as a public function, or start the module name with _ otherwise. However, since this function seems to be purely an implementation detail of _.toArray, it should probably be internal, and maybe it should even be defined inside modules/toArray.js.

var data,
result = [];

while (!(data = iterator.next()).done) {
result.push(data.value);
}
return result;
}
4 changes: 3 additions & 1 deletion modules/toArray.js
Expand Up @@ -5,7 +5,8 @@ import isArrayLike from './_isArrayLike.js';
import map from './map.js';
import identity from './identity.js';
import values from './values.js';

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This blank line should stay.

import isIterable from './isIterable.js'
import iteratorToArray from './iteratorToArray'
// Safely create a real, live array from anything iterable.
var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
export default function toArray(obj) {
Expand All @@ -16,5 +17,6 @@ export default function toArray(obj) {
return obj.match(reStrSymbol);
}
if (isArrayLike(obj)) return map(obj, identity);
else if (isIterable(obj)) return iteratorToArray(obj[Symbol.iterator])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding a feature to Underscore, you always also have to add tests to verify that the feature works as intended. This is especially important because this enables us to notice when a later change breaks a pre-existing feature.

I'm mentioning this here because _.toArray is already a public function. If isIterable or iteratorToArray was public as well, though, you'd have to add tests for that, too.

return values(obj);
}