7 ms·
Coming from a heavy TS background into a go-forward company, I’d say the main thing you get with async is it makes it incredibly obvious when computation can be
by explaininjs 2y ago
Coming from a heavy TS background into a go-forward company, I’d say the main thing you get with async is it makes it incredibly obvious when computation can be performed non-sequentially (async…). For example, It’s very common to see the below in go code:
a := &blah{}
rA, err := engine.doSomethingWithA()
b := &bloop{}
rB, err := engine.doSomethingWithB()
This might have started out with both the doSomethings being very quick painless procedures. But over time they’ve grown into behemoth network requests and very thing is slow and crappy. No, it’s not exactly hard to spin up a go routine to handle the work concurrently, but it’s not trivial either - and importantly, it’s not immediately obvious that this would be a good idea.
Contrast to TS:
let a = {blah}
let [rA, err] = engine.doSomethingWithA()
let b = {bloop}
let [rB, err] engine.doSomethingWithB()
Now, time passes, you perform that behemoth slowing down of the doSomethings. You are forced by the type system to change this:
let a = {blah}
let [rA, err] = await engine.doSomethingWithA()
let b = {bloop}
let [rB, err] await engine.doSomethingWithB()
It’s now immediately obvious that you might want to run these two procedures concurrently. Obviously you will need to check the engine code, but any programmer worth their salt should at least seek to investigate concurrency when making that change.
I wouldn’t be bringing this up if I hadn’t made 10x+ performance improvements to critical services within a month of starting at a new company in a new language on a service where the handful of experienced go programmers on the team had no idea why their code was taking so long.
- hombre_fatal 2y agoOf course, the other nice thing about the JS example compared to Go is that it's trivial at the callsite to do this: const [[rA, errA], [rB, errB]] = await Promise.all([ engine.doSomethingWithA(), engine.doSomethingWithB() ]) At least these days you can ask an LLM to write the WaitGroup boilerplate for you in Go.
- explaininjs 2y agoIndeed. And breakpoints and stepping across concurrent context actually works in JS, which is nice.
- Kamq 2y agoYeah, go's a little boilerplatey, but you have to option to run two sync things concurrently as well with something like: type result[T any] struct { el T err error } chanA := make(chan result[aResultType]) chanB := make(chan result[bResultType]) go func() { defer close(chanA) a := &blah{} rA, err := engine.doSomethingWithA() chanA <- result[aResultType]{ el: rA, err: err, } }() go func() { defer close(chanB) b := &bloop{} rB, err := engine.doSomethingWithB() chanB <- result[bResultType]{ el: rB, err: err, } } resultA := <- chanA resultB := <- chanB
- deleted 2y ago[deleted]
- dumbo-octopus 2y agoOne could theoretically pull out the shared boilerplate to a utility function like: func runTask[T any](task func() (T, error)) chan result[T] { ch := make(chan result[T]) go func() { defer close(ch) res, err := task() ch <- result[T]{ el:res, err:err } }() return ch } Does that sort of thing happen much in practice?
- jerf 2y agoYes, it does, and Go is perfectly capable of it, and many libraries exist for you to choose which exact method suits your problem and temperment. One of the common pasttimes in the threaded versus async debate is to present code in which one side uses all sorts of helpers and patterns and libraries and the other side is presented through writing it "raw". The great-grandparent of my post here is guilty of this. While there are interesting reasons to debate threaded versus async code, this is not one of them. Both of them are absolutely capable of writing the moral equivalent of "output = parallel_map(myMapFunc, input)" and all similar operations to within practical epsilon of each other, and anyone citing this sort of thing as an argument on either side should probably be ignored. And both languages will feature code written by people who don't know that, and it shouldn't count against either.
- masklinn 2y agoWhich has to do with the incredible lack of expressivity of Go, not with the concurrency model. Nothing precludes doing exactly the same thing with thread-like constructs in an expressive language. Not to mention waitgroups are way overkill for this. You’d just use a channel or two. Or an errgroup if you want to be fancy.
- sapiogram 2y agoWaitGroup/ErrGroup doesn't even work here, because the functions return data. I mean, you can use ErrGroup, but it requires additional error-prone concurrency orchestration to work.
- jayd16 2y agoHonestly, despite that blog, async coloring is a feature. The pattern enforces implicit critical sections between yields and the coloring is how the dev can know what will yield.
- explaininjs 2y agoYes. That blog has probably done more to negatively impact the industry than any other written work I know.
- throwitaway1123 2y agoThis is a really interesting point. You almost never hear async function coloring being conceptualized as a feature rather than a hindrance. Async function coloring is kind of analogous to the borrow checker in Rust. It makes you think about concurrency the same way that the borrow checker makes you think about memory ownership and lifetimes.
- bloppe 2y agoasync is a great feature if you use it from square 1. If you start with a legacy codebase using callbacks and try to port it incrementally to async, you're gonna have a bad time. Otherwise, it's definitely a feature
- throwitaway1123 2y agoYeah upgrading a legacy codebase that uses callbacks is not fun, but if the callback functions follow the Node error first value second convention, then it's a little bit easier because you can just use `util.promisify` to convert them to promises in Node. There's also the new Promise.withResolvers method which helps a bit too [1]. [1] https://github.com/tc39/proposal-promise-with-resolvers https://github.com/tc39/proposal-promise-with-resolvers
- fulafel 2y agoFor some reason requiring the programmer to use additional syntax at the call site to mark behavioural properties of called functions is not a popular language feature generally. I guess eg TypeScript could add it as a user extensible feature. Would it be useful to be able to require things like this in your internal API? let gizmos = nocheckperms lookupRequestedGizmos(request);
- asp_hornet 2y agoOnly if those doSomething methods were written as asynchronous to begin with.in your original example, doSomethingA was simple, why would it be an async method. If your answer is write every method async for a rainy day, then whats the point.
- explaininjs 2y agoNo… that’s the whole point. If you change them to be async, the language forces you to go and rethink what implications that has for the callers. This is a good thing, dumbly sequenced operations are terrible UX. And UX is far more important than whatever it is they call “DX”.
- asp_hornet 2y agoI’d rather just stick to languages that don’t have colored functions. You don’t need to be forced to think about this. Your tools manufactured this issue.