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
Disallow for-of #1122
Comments
|
Absolutely, thanks! Want to make a PR? it will be semver-major, so i won't be able to merge it immediately, but having it queued up would be great. |
|
Looks like I was beat to the punch by mere hours. Thanks @SimenB. |
|
What should I do in this case: async function foo(args) {
for (const arg of args) {
await bar(arg);
}
} |
|
@dplusic in nice, simple ES6: function foo(args) {
return args.reduce((prev, arg) => prev.then(() => bar(arg), Promise.resolve());
} |
|
It works. Thanks! |
|
@ljharb - what about generators? I have a function like this that flattens objects recursively, so function* flatten(obj, str = '') {
if (obj) {
for (const name of Object.getOwnPropertyNames(obj)) {
if (!Array.isArray(obj) && typeof obj[name] === 'object') {
yield* flatten(obj[name], str ? `${str}.${name}` : name);
} else {
yield {
[`${str}.${name}`]: obj[name],
};
}
}
}
}Which is turned into a 'flat' object with I'm probably overlooking an obvious equivalent with map, but a generator solves this quite neatly... |
|
@leebenson generators are not allowed in our guide. As for an alternative, I'm not sure if this actually works, but perhaps something like: function flattenObject(obj, prefix = '') {
return Object.entries(obj).reduce((acc, [key, val]) => {
const prefixedKey = prefix ? `${prefix}.${key}` : key;
if (val && !Array.isArray(val) && typeof val === 'object') {
return Object.assign(acc, flattenObject(val, prefixedKey));
}
return Object.assign(acc, { [prefixedKey]: val });
}, {});
} |
|
Thanks @ljharb. This minor tweak works perfectly: function flattenObject(obj, prefix = '') {
return Object.entries(obj).reduce((acc, [key, val]) => {
const prefixedKey = prefix ? `${prefix}.${key}` : key;
if (val && !Array.isArray(val) && typeof val === 'object') {
return Object.assign(acc, flattenObject(val, prefixedKey));
}
return Object.assign(acc, { [prefixedKey]: val });
}, {});
} |
|
Of course readability is subjective, but I find loops far more unreadable than a functional alternative. |
|
That's interesting. Of course I don't have any empirical evidence for this, but I bet it'd take the average programer an order of magnitude more time to understand your snippet vs what @dplusic posted. I hope this fad of functional-at-all-cost is over soon. There's a reason why Haskell hasn't gone mainstream. |
|
I do think @minexew has a point. With native Personal preference, of course. |
|
i’d actually argue that for..of should be used because of functional style:
|
|
@flying-sheep it's a functional evolutionary step to |
|
behold: function* map(callable) {
for (const element of this) {
yield callable(element)
}
}
function* filter(predicate) {
for (const element of this) {
if (predicate(element)) yield element
}
}
const odd = '12345'
::map(parseFloat)
::filter(e => e % 2)
both only have side effects (if anything, |
|
@flying-sheep lol fair point, both are for side effects since for..of isn't an expression. forget that part. The |
Kinda tangentially related: would you be interested in a config for server-side only projects? I've used If you're interested at all, I can create a new issue for it to discuss its merits there? |
|
Server-side only projects should be using babel also. |
|
Why? With async/await landing, the only syntax I care about is object spread, and it's OK to use |
|
For one, old syntax is generally faster than new syntax, since it's had time to be optimized. Additionally, there'll always be new syntax you want to use, and if your build process is already in place, it's trivial to add that. A build step shouldn't add overhead, since you should be running your linter and your tests prior to code going to production anyways - just add the build step into there. |
|
@ljharb maybe i didn’t understand you: you argued that it’s an evolutionary step towards functional array methods. i showed that this isn’t the case. my use of generators is irrelevant. e.g. here’s a map implementation without anything you mentioned: Array.prototype.map = function map(callback) {
const mapped = []
for (const element of this) {
mapped.push(callback(element))
}
return mapped
} |
|
The given implementation of map - or any other abstraction - is entirely irrelevant. forEach is an array prototype method, just like the functional array prototype methods. |
|
I use my babel-preset-node7 for server side use of the airbnb preset. It adds the (very few) missing pieces -- trailing function commas, etc -- and avoids transpiling native features like generators, async/await, etc. The extra build step makes sense in my case, because I'm writing universal code that targets both the server and the browser, so a build step is necessary anyway. I use generators, async/await, etc freely, and provide a different babel config to each target in webpack. In my use case, adding browser polyfills/runtimes has a negligible effect on the bundle size. My total app is less than 200kb gzipped, with React, Mobx, es2015 polyfills, all app code, etc. For the most part, I find the airbnb preset to match my own personal stylistic preferences, but I do agree that eliminating In my case, adding a custom rule for |
|
yeah, for..of and generators can be used e.g. for efficient parsers (you delegate to sub parsers via JS is always the slow kid on the block. maybe in a year or so, we’ll see “map considered harmful” posts that praise how much awesomer generators and comprehensions are. |
|
Comprehensions are never coming to JS, so I doubt you'll ever see that post :-) |
I can't figure what's not OK with them in some cases (like when working on async code). Airbnbs argumentation is about immutability. Mine is about readability and ease of use. I like iterators, they are great! See https://github.com/airbnb/javascript#iterators-and-generators and airbnb/javascript#1122 (comment)
|
Just found this thread. Hope I'm not too late to ask questions. What's the equivalent solution if not using for-of loop ? |
|
@fleksin someFunc = array => array.every(item => !somePredicate(item));Alternatively, if "any item produces someFunc = array => array.some(item => somePredicate(item));but you'll want to confirm that the behavior matches your expectations for an empty array. |
|
@hmagdy No, they are not same. |
|
Indeed, you'd need the (not weird - a normal part of JS and functional programming) reduce instead. |
The former actually results in a “SyntaxError: await is only valid in async function” as expected. The arrow function isn’t I don’t think this discussion is helpful anymore. Everything’s been said on why the rule is there:
I think the only thing left to discuss is what I said in #1122 (comment), and I filed #2019 for that. |
|
To clarify; we recommend forEach because loops should basically always be avoided. |
|
People don’t agree with you here, see the discussion in #2019 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I want to process an iterator. Is there a functional way of doing this? let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
} |
|
@Ghost---Shadow |
|
I'm much faster and far less prone to errors if I can understand the code I'm looking at. All things equal, readability and clarity should be first priority. for (const person of queue)
await person.ready()queue.reduce((prev, person) => prev.then(() => person.ready(), Promise.resolve());How fast can you decipher what the function does and spot the error? Furthermore, how would you handle this logic in a readable way? for (const person of queue){
await person.isReady()
queue.push(...person.expectedGuests)
}or this for (const [i, person] of queue.entries()){
await person.isReady()
queue.push(...person.expectedGuests)
queue.splice(i, person.accompanyingGuests.length)
}And what if we decide to add a timer that bumps people to the back if they're too slow? I can add extra logic while keeping things readable and easy to reason about. |
|
@jakobrosenberg quite fast, but obv that's subjective. However, it's pretty easy to make an abstraction around that reduce like Same feedback on the others - using a loop requires someone to infer your intention, using a named abstraction doesn't. |
You can abstract a As Capone said: You can get much further with clear intentions and readable code than you can with clear intentions alone. |
|
@jakobrosenberg that's totally true! and if you do that, and you don't transpile such that you'd include regenerator-runtime when using |
|
Also if the accumulator is an array or object you might tend to spread the next item in, which is vastly less efficient than writing some more lines of code and mutating accumulator. At that point you haven't really saved any code compared to a loop, just written more opaque and slow code because someone said it's more "FP". So I use reduce if I'm actually reducing stuff or it's really cleaner, in this case I agree with @jakobrosenberg that his code is a lot easier to understand, which is much like try/catch/await being easier to understand than Promise then/catch |
|
@DominicTobias-b1 in the latest version of all browsers. Despite marketing, no browsers are "evergreen" according to the google analytics of major websites I've been able to review over the last couple years. (Nothing but safari will likely ever support PTC - which is not an optimization - so that's not really relevant to discuss) Performance isn't important, readability is. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The original intention was to disallow usage of iterators. airbnb/javascript#1122 Of course we can improve the error message a little bit, but I don't think it's worth the extra effort for now.
The original intention was to disallow usage of iterators. airbnb/javascript#1122 Of course we can improve the error message a little bit, but I don't think it's worth the extra effort for now.
The style guide 11.1 says that you shouldn't use
for-ofand instead should useforEach(which I completely agree with).That said, the ESLint rule
no-iteratordoesn't seem to enforce this, and the entry forno-restricted-syntaxcontainsForInStatementbut notForOfStatement.Can we add
ForOfStatementtono-restricted-syntax(or is the a better way of restrictingfor-ofstatements?The text was updated successfully, but these errors were encountered: