Live data from Hacker News

Callback Hell (2016)

callbackhell.com

31–40 of 170 posts

Re: Callback Hell (2016)

#31
post #29

As a C programmer working with a large codebase, I have come to HATE callbacks. Seriously, the worst feeling ever is tracing through a huge function tree, only to run into function pointer dereference. Then you have to go on a wild goose chase to find out when, where and what it will be assigned to. STATIC TYPES PEOPLE.

As a C beginner I don't like them either. But are there techniques to avoid them? Take for instance nghttp2 which needs callbacks to handle I/O.

Re: Callback Hell (2016)

#32

As a curious and perhaps naive aside, I've been wondering why the programmer should need to care about asynchronous execution of code at all. Can't it all be abstracted under a procedural layer and let the OS worry about not blocking anything? The advent of promises, async.js, and other paradigms tell me that people still kind of want to write code that does one thing after another, then another, then another.

The problem is how it interacts with other effects (e.g. mutable state): https://glyph.twistedmatrix.com/2014/02/unyielding.html .

I think one of the reasons people don't appreciate the importance of explicit effect management is that any one unmanaged effect in your program is usually ok. It's the interaction of multiple unmanaged effects that causes problems.

Re: Callback Hell (2016)

#33
post #29

As a C programmer working with a large codebase, I have come to HATE callbacks. Seriously, the worst feeling ever is tracing through a huge function tree, only to run into function pointer dereference. Then you have to go on a wild goose chase to find out when, where and what it will be assigned to. STATIC TYPES PEOPLE.

What does it have to do with static typing? Function pointers are perfectly valid types. Also, can't you just use a debugger?

Re: Callback Hell (2016)

#34

As a curious and perhaps naive aside, I've been wondering why the programmer should need to care about asynchronous execution of code at all. Can't it all be abstracted under a procedural layer and let the OS worry about not blocking anything? The advent of promises, async.js, and other paradigms tell me that people still kind of want to write code that does one thing after another, then another, then another.

From a mathematical perspective, synchronous code is strictly more powerful and say-what-i-mean. Asynchronous code is closer to how a (modern) computing system functions in practice.

And yes, a large percentage of our use-cases for callbacks tend to be of the "yield execution of a multi-step task" form. But it isn't just callbacks, it's anything of a "concurrent-and-branching" type flow, so game AIs, UI, etc. also run into this stuff a lot. Sometimes it can be dealt with informally, other times it needs more structure for engineering with confidence to be possible.

A formalized finite state machine is one solution to making sense of the problem in a more generalized way: instead of handing off execution flow based on a morass of polling and callbacks, there's a big switch statement to represent the FSM. The end of each branch of the FSM dictates what the next branch is by setting a branching variable. And every branch ultimately calls into the FSM again, either via a callback or an external loop. This style has the upside of an easier debug trace and the downside of another variable to track and potentially mishandle. (overall, a net gain as the FSM scales up)

A more composable form of FSM used in game AI is the "behavior tree", which encodes blocks of logic in each node of the tree, each node returning a status code: success, failure, in-progress, and optionally taking an action affecting external state like "play animation" or "fire weapon". The general progression is a walk from roots to leaves, with some nodes used to determine the exact sequencing(left to right, pick random, etc.) and other nodes used to tell the tree to yield execution or restart processing by sending back a status code. With behavior trees, there's a substantial win for reusability since the nodes cleanly delineate their needs for parameters, allocation, yielding, etc.

At the language level we're still mostly catching up on strategies that give the idioms comfortable syntax. Yielding iterators (such as those in Python) and promises can sometimes substitute for the simple case of calling a sequence. Full-blown continuations exist in several languages and are very powerful but also encode too much program scope to be used at scale or for long-running, persistent tasks.

Re: Callback Hell (2016)

#37
post #26

Earlier quoted context omitted.

Your try catch is useless here, AFAIK JSON.stringify doesn't throw. and you can't catch someCustomHttpGet() "exceptions" if the latter is supposed to be asynchronous. relevant: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...

Yeah, I messed it up when quickly typing the code without thinking, it should've been JSON.parse, I fixed it now. Also regarding that article: you can call async functions from non-async functions - you will simply get the Promise object instead of the data. If you're using TypeScript, your editor will immediately highlight the error if you try to use that Promise object as though it were the data, so zero chance of…

> Also regarding that article: you can call async functions from non-async functions - you will simply get the Promise object instead of the data.

    async function() getZ(){ return promise }

    function() getX(){
        return x = y * getZ()
    }

    // sync call globally.
    let x = getX(); // NaN
async/await makes things more readable, it doesn't change the issue of leaky abstraction and you need to know whether the call is blocking or non blocking when calling getZ and using a function that calls getZ. What should you do? assume everything is asynchronous by default?

Imagine you have a validation lib, you don't need it to be asynchronous to validate a string of length > 10, but you need to rewrite your whole API if you need to validate the fact a unique index has to be validated from a database.

Re: Callback Hell (2016)

#38
post #9

As a curious and perhaps naive aside, I've been wondering why the programmer should need to care about asynchronous execution of code at all. Can't it all be abstracted under a procedural layer and let the OS worry about not blocking anything? The advent of promises, async.js, and other paradigms tell me that people still kind of want to write code that does one thing after another, then another, then another.

Openresty does this quite nicely with Lua: you just write your code as normal and it handles the rest in the background. http://leafo.net/posts/itchio-and-coroutines.html

Io language does the same.

The problem is that most languages are not powerful enough to easily express this. You basically need call/cc in some form or built-in coroutines. And a library/framework designed for this.

Re: Callback Hell (2016)

#39

As a curious and perhaps naive aside, I've been wondering why the programmer should need to care about asynchronous execution of code at all. Can't it all be abstracted under a procedural layer and let the OS worry about not blocking anything? The advent of promises, async.js, and other paradigms tell me that people still kind of want to write code that does one thing after another, then another, then another.

That's exactly what monadic code in Haskell does --- a Haskell `do` block sets up a callback chain, and then when the runtime evaluates the chain, all the entries in the chain happen in the right order. Dependencies between items in the chain and between different chains happen automagically.

Because all mutable state is encapsulated inside the monad and only actually takes effect when the state changes get applied to the outside world, it also allows really cool things like abandoning and retrying state changes if the state's not right. This allows really cool things like STM's 'atomically' operation. Behind the scenes it'll roll back and retry the operation whenever necessary --- but you never need to care: the effect of the operation gets applied to the outside world exactly once.

Re: Callback Hell (2016)

#40
post #6

> In other languages like C, Ruby or Python there is the expectation that whatever happens on line 1 will finish before the code on line 2 starts running and so on down the file. As you will learn, JavaScript is different. A lot of beginner guides to various programming languages make this mistake of associating a certain property with a language as if it's inherent. In this case, async code - while sold as a main fe…

> A lot of beginner guides to various programming languages make this mistake of associating a certain property with a language as if it's inherent. In this case, async code - while sold as a main feature of the NodeJS platform - is in no way exclusive to, or even an inherent part of, Javascript/ES the language.

Exclusive no, but considering JS's original environment and constraints it is in fact pretty inherent. `setTimeout` is part of the core language and has been for a long time, a blocking `sleep` is not.

Post reply on HN