Concurrency: from Promise orchestration to goroutines and channels
Map JavaScript's async and Promise habits onto Go's goroutines and channels.
by the end of this lesson you can
- →Uses goroutines intentionally
- →Makes coordination explicit
- →Accounts for failure instead of assuming async errors behave like Promise chains
Overview
JavaScript developers already think in terms of the event loop, async work, and Promise orchestration. Go also cares about concurrency, but its tools are goroutines, channels, and explicit coordination patterns rather than Promise chains.
In JavaScript/Node, you often
coordinate concurrent work with Promise combinators and async functions.
In Go, the common pattern is
to launch concurrent work with goroutines and coordinate results explicitly through channels or synchronization tools.
why this difference matters
The key shift is that concurrency in Go feels more like part of ordinary program structure and less like a special async layer.
JavaScript/Node
const user = await fetchUser(id);Go
ch := make(chan User)
go func() {
user, _ := fetchUser(id)
ch <- user
}()
user := <-chDeeper comparison
JavaScript/Node version
async function loadDashboard(id) {
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id),
]);
return { user, posts };
}Go version
func loadDashboard(id int) (User, []Post, error) {
userCh := make(chan struct {
user User
err error
})
postsCh := make(chan struct {
posts []Post
err error
})
go func() {
user, err := fetchUser(id)
userCh <- struct {
user User
err error
}{user: user, err: err}
}()
go func() {
posts, err := fetchPosts(id)
postsCh <- struct {
posts []Post
err error
}{posts: posts, err: err}
}()
userResult := <-userCh
if userResult.err != nil {
return User{}, nil, userResult.err
}
postsResult := <-postsCh
if postsResult.err != nil {
return User{}, nil, postsResult.err
}
return userResult.user, postsResult.posts, nil
}Reflect
When does Go concurrency feel simpler than Promise-based orchestration, and when does it demand more discipline?
what a strong answer notices
A strong answer mentions lightweight concurrency startup, but also explicit coordination and error handling responsibilities.
Rewrite
Translate this Promise-based JavaScript shape into a Go-style concurrent design.
Rewrite this JavaScript/Node
async function fetchPair(id) {
const user = await fetchUser(id);
const posts = await fetchPosts(id);
return { user, posts };
}what good looks like
- Uses goroutines intentionally
- Makes coordination explicit
- Accounts for failure instead of assuming async errors behave like Promise chains
Practice
Design a Go function that loads a profile and billing summary concurrently and returns a combined response only when both succeed.
success criteria
- Explains how results are coordinated
- Handles at least one failure path clearly
- Keeps the final result assembly separate from the concurrent fetch logic
Common mistakes
- Expecting channels to behave like Promises or streams with the same ergonomics.
- Adding goroutines before the sequential version is clear.
- Ignoring cleanup and failure coordination because Promise helpers used to hide more of it.
takeaways
- ●The key shift is that concurrency in Go feels more like part of ordinary program structure and less like a special async layer.
- ●A strong answer mentions lightweight concurrency startup, but also explicit coordination and error handling responsibilities.
- ●Explains how results are coordinated