Earlier quoted context omitted.
You're just using the channel as a future. None of this is really problematic at the top level view of things. But when you need to compose libraries or applications that make use of these things - yes, even channels in Go - you can start running into problems. Especially if you don't actually control the process you're running in. This is why promises and async/await really exist. E.g. if you have code you need to f…
I don’t see how futures can be more flexible than go routines. Could you explain that more? And why couldn’t you just spawn a goroutine to avoid blocking your main thread?
A simple example of where this might be useful is in UI code. Every UI framework I've worked in has a single UI thread, where all UI changes must go - e.g. adding a button, adding text to the UI, etc. At the same time, you don't want to block the UI thread, because that will cause the UI to become unresponsive.
A common thing you might do in a UI is: on button click, make an API call, then update the UI with that data.
The on button click will be called from the UI thread. You don't want to make the API call there though because that would block the UI thread. So you spin it off into a worker thread. But after that worker finishes, you need to regain control of the UI thread so you can update the UI.
The way you traditionally do this is you have some kind of main loop which is running the UI thread, which has (among other things) some kind of queue of functions it should call (I assume it would be a channel in Go). So your worker thread will hold onto a reference to this queue. When it finishes, it pushes a function call onto the queue. The main UI loop occasionally checks this queue and calls all functions in it. So eventually it gets called, allowing you to update the UI in the UI thread.
This can be fine for simple scenarios. But if you have deeply nested or complex interactions between multiple threads, the code can get confusing and turn into "callback hell". This is the problem that was ran into in early versions of nodejs. This is the problem async/await was intended to solve. It's a bigger problem in nodejs due to its design, but it can still be useful in other languages that don't share that design depending upon the application you're writing.