You can build really complex dynamic async chains in JS very easily because of this. It's hard to explain why it's so nice until you try it.
In JS, I can do something like:
Promise.resolve().then(() => {
let innerPromiseChain = serialOperation1()
.then(serialOperation2);
if (somethingIsTrue) {
// Only inject serialOperation3 at the end of the
// innerPromiseChain if somethingIsTrue
return innerPromiseChain.then(serialOperation3);
}
// else return the inner chain without serialOperation3
return innerPromiseChain;
})
.then((resultOfLastSerialOperation) => { // resultOfLastSerialOperation could be the result of
// either serialOperation2 or serialOperation3 depending
// on the value of somethingIsTrue
// parallelOperation1 and parallelOperation2 will be
// executed in parallel.
return Promise.all(parallelOperation1(), parallelOperation2());
})
.then((parallelOp1Result, parallelOp2Result) => { // This will be called once the previous parallel
// operations have both finished (therefore the
// whole chain has been processed).
});For imperative code the goal is to write code that is just as clear as writing a single threaded algorithm: a simple list of steps. Promises and callbacks DO NOT ACHIEVE THIS GOAL.
That stackoverflow post showed a python solution that's infinitely cleaner than js. Python skipped callbacks and promise chains and jumped directly to the best way to do async operations... async/await.
You want to shove a parallel set in an async chain of operations? Here's the way to do serial and parallel async operations in Python:
async def main():
# run them sequentially (but the loop can do other stuff in the meanwhile)
body1 = await get_body('http://httpbin.org/ip')
print body1
body2 = await get_body('http://httpbin.org/user-agent')
print body2
# run them in parallel
task1 = get_body('http://httpbin.org/ip')
task2 = get_body('http://httpbin.org/user-agent')
for body in await asyncio.gather(task1, task2):
print(body)
You see that code? Way more clearer way more understandable than your promise chain. Python has it's problems but your promise code is not powerful at all, it leads to over complicated code and just slightly better than callbacks.You can do pretty much exactly the same with JS but I do think that there is some merit to this intermediate step of having the then() clauses for backwards-compatibility because you can't always guarantee that the caller will in be an async function.
Async/await is an all-or-nothing improvement not an incremental one.
This is a synchronous callback (but callback nonetheless), that takes an array of numbers, and adds its entries:
arr.reduce(function(acc, item){ return acc + item; });
There's some boilerplate in this, so as per latest JS specs, you can remove all that and rewrite it as:
arr.reduce( (x, y) => x + y);
Then there's asynchronous callback. Most of "callback-hell" stems from these type of callbacks, because you would do "do-this-then-do-this-then-the-other-thing", and nest these one within other.
We realized that callback-hell prevents us from using return or handle errors in a clean & reliable way, so we went to promises.
Then promises turned out to be quite a pain too. Then generators, and now we have settled for async-await.
But in no way that "hell" is acceptable!
let doubled = nums.map(x => x * 2)
and this: def double(x):
return x * 2
doubled = map(double, nums)
(my Python is pretty rusty so maybe there's a better way of doing that) doubled = map(lambda x: x * 2, nums)
JS it's just not as explicit with what you're exactly doing. def foo():
# ... four lines here ...
modified = map(foo, items)For example, `f` is a refactoring of an anonymous function:
function f (item) {
return {
// foo properties
}
}
const fooItems = values.map(item => f)
Imagine that you have to map over several different collections of items, and generating the lists `foo`, `bar`, and `baz`. You have to either name the functions we pass to map `f`, `g`, and `h` (which I bet our reviewers would hate), or `fooMapFunc`, `barMapFunc`, `bazMapFunc`. It just pollutes the namespace that I have to keep in my head, because I have to wonder "is this used somewhere else?".Moreover, is it _more readable_ to define the helpers first, and then the collections that are made by using them, or to define the pairs (fooMapFunc, fooItems) in sequence? This could easily be felt one way or the other, and both are valid, but leads to code review holy wars. "I feel this is more readable / I feel exactly opposite".
For comparison, the anonymous version of this similar operation:
const fooItems = values.map((item) => {
return {
// foo properties
}
})
const barItems = values.map((item) => {
return {
// bar properties
}
})
const bazItems = values.map((item) => {
return {
// baz properties
}
})
In this case, the anonymous function is clearly not usable anywhere else, so it's easier to be sure that it's something one can change, and completely removes the chance of holy war over readability of whether to define helper functions together or with their collection.A nested function is a simple abstraction in a narrow scope. Both the abstraction and the name are easy to change, so the name only has to describe what the function does now. When that's hard, it's often because the function does too much, does too little, or belongs somewhere else. Likewise, people arguing about the organization of inner functions is usually a sign the outer function should be refactored. Anonymous functions are appropriate when names would be almost redundant, not when coming up with a name is hard.
My editor can show me where a function is used; it can't summarize what a function does. Either way, you have to find where "fooItems" is used to know if you can modify the function.
Conversion functions are very easy to name ("itemToFoo"). Idiomatic Python would just use a loop:
foo_items = []
for item in values:
foo_items.append({
# foo properties
}) return [x*2 for x in nums]Typically in programming you don't often encounter the need to pass a multiline anonymous function into another function, it leads to confusing code. Async/await gets rid of this bs all together which is the pythonic way to do async.
btw pythonic code for maps or filters is to use list comprehensions so
doubled = [x2 for x in nums]
or if you want to use a anon function:
doubled = [(lambda y: y2)(x) for x in nums]
In my experience (I write mostly JS but also a lot of C# and, not as much, Rust), passing functions around is very easy to read and reason about. Functional programming "Just Makes Sense™" to me. I think it's just an exposure thing. Many people find Python's significant whitespace cleaner. I disagree but I promise you it's because I haven't spend much time with it. If I wrote a lot more Python, I'd probably grow to like it.
>I'd say that anyone who's complaining about callback hell isn't up-to-date with the JS world. Promises have fixed this problem and, as you pointed out, async/await make it even nicer.
The latest node api still uses callbacks. Promises haven't fixed this problem, it's just a bandaid on stab wound. Async/await is the proper way to deal with this problem and python supports it so there's no upside for node in this area.
doubled = [x * 2 for x in nums]JS:
doThing(function(){
blah();
de();
blah();
});
Python: def thing_doer():
blah()
de()
blah()
doThing(thing_doer)You can pass a function as a callback or something else in python. You just give the function a name then pass it. No function literals.
Also he's talking about async/await in koa... does this fool realize that async/await is built into python?
I'm proficient in both python and js and I have to tell you, python is light years ahead of js in terms of language design.
Sounds like you're more pissed off at the article because it contradicts your views than anything else. Sounds like you didn't even pay full attention to it.
Yes, those who assumed I meant you can't pass an anonymous multi-line function to another were obviously right -- it dazzles me people spent so much time arguing over this point. There's not much else to it.
I'll just say: that's definitely NOT the sole reason -- I point out several other factors that weighed in my decision in making JS my #1 choice. Above all, after seeing a few successful large projects deployed (and no issues in production), I just came to trust the Node.js stability and ecosystem.
There are sentences which are telling a huge incompetence about the person using these.
This sounds the same story to me as "We used tech X, but X is shit/can't do something so we switched to Y, and Y is awesome and fast." Where the person switching was just incompetent with X but it would have been perfectly solvable and they just do a totally different thing with Y.
In the specific "passing a function" case, yes, it's impossible to pass only a function body, but I think it's impossible to do in JavaScript also? (correct me if I'm wrong), because you pass in function. You can do the same in Python by defining a function in the same scope and passing that in. If you don't like that, it's just one opinion, not necessarily good or bad. For example, I find it ugly and difficult to handle nesting this multiple times (defining another anonymous function inside an anonymous function) which is called "callback hell" if I understand right, but that's just my opinion also, because people seem to able to accept this hell. :)