Preface I was learning Golang recently. What interests me in Golang is the concurrency model. In Golang, we use goroutines to execute asynchronous tasks while these goroutines communicate with each other via the data structure called . goroutine + channel Channel As a frontend developer, I found that the goroutine + channel model in Golang is much like the event loop in Javascript. Javascript V8 engine uses the main thread to execute synchronous code while Golang uses the main goroutine. Javascript V8 engine maintains a thread pool to execute asynchronous code while Golang uses multiple goroutines. Javascript threads communicate with the main thread via a constantly polling event loop while Golang goroutines communicate with each other via . Channel So I am wondering if there is a way that we could control asynchronous tasks in Golang via pattern just like what we do in Javascript ES7. The answer is yes! Async/Await For more details → https://github.com/sun0day/async Async/Await Before we continue, let’s give a flashback about the common asynchronous model. There are two kinds of common asynchronous models: the blocking asynchronous models & non-blocking asynchronous models. The difference between a blocking asynchronous model and a non-blocking asynchronous model is whether the synchronous code execution should wait for the asynchronous task response to continue. In Javascript ES7, we use reserved word to tag a as an asynchronous task. Once you call a , it will be sent to another sub-thread to execute. Then the rest synchronous code will be executed immediately no matter whether the is done. The calling flow above is non-blocking asynchronous. We can also transform the calling flow to be blocking asynchronous using reserved word. async function async function async function async funciton async function await async function nonBlock() {...} async function block() {...} async function main() { nonBlock() // will not block main await block() // will block main } Golang provides a more granular and flexible way to control asynchronous task execution. But flexibility also means we have to write more code to control asynchronous task execution. And what’s worse is that we have to deal with exceptions such as goroutine leak, deadlock, panic, etc. Introducing Async/Await pattern in golang can help Goer to write more neat, readable, and robust code. Async Implementation in Golang In Golang we can convert a synchronous to be asynchronous using a named . The type definition is: func func Async Async func Async[V any](f func () V) func() *AsyncTask[V] {...} accepts a as a parameter and returns a new which will create a named . You should tell what the type of is via the generic parameter . represents the asynchronous task created by . It has two properties: & . Async func func struct AsyncTask Async return f V AsyncTask Async data err type AsyncTask[V any] struct { data chan V err chan any } We can observe result through and the possible error through . Then let's take a look at the internals of . f() AsyncTask.data AsyncTask.err Async func Async[V any](f func () V) func() *AsyncTask[V] { exec := func() *AsyncTask[V] { data, err := handle[V](f) return &AsyncTask[V]{data: data, err: err} } return exec } A named will create a pointer, inside , handles the synchronous result and possible error asynchronously. Both and capacity are set to 1 to prevent goroutine leaks. Once is done, the sub goroutine created by can exit immediately regardless of whether there are other goroutines receiving and . func exec AsyncTask exec handle f data Channel err Channel f() handle data err func handle[V any](f func () V) (chan V, chan any) { data := make(chan V, 1) err := make(chan any, 1) go func() { var result V defer func() { if e:= recover(); e == nil { data <- result } else { err <- e } close(data) close(err) }() result = f() }() return data, err } The example below shows how encapsulates and how works asynchronously. Async f f func main() { a := 1 b := 2 af := Async[int](func() int { c := a + b fmt.Println("f() result is", c) return c }) fmt.Println("sync start, goroutine=", runtime.NumGoroutine()) af() fmt.Println("sync end, goroutine=", runtime.NumGoroutine()) time.Sleep(1 * time.Second) fmt.Println("async end, goroutine=", runtime.NumGoroutine()) } /* stdout: sync start, goroutine=1 sync end, goroutine=2 f() result: 3 async end, goroutine=1 */ Await Implementation in Golang implementation in Golang is simple. What does is waiting for or sending data. Await Await AsyncTask.data AsyncTask.err func Await[V any](t *AsyncTask[V]) (V, any) { var data V var err any select { case err := <-t.err: return data, err case data := <-t.data: return data, err } } The example below shows how waits for response. Await Async func main() { a := 1 b := 2 af1 := Async[int](func() int { c := a + b return c }) af2 := Async[int](func() int { panic("f() error") }) fmt.Printf("sync start, goroutine=%d\n", runtime.NumGoroutine()) data, _ := Await[int](af1()) _, err := Await[int](af2()) fmt.Printf("sync end, goroutine=%d, af1() result=%d, af2() result='%s'\n", runtime.NumGoroutine(), data, err) } /* stdout sync start, goroutine=1 sync end, goroutine=1, af1() result=3, af2() result='f() error' */ Conclusion This article shows how to implement ES7-style Async/Await API in Golang via goroutine and . In addition to the above definition, we still need to consider its execution result and state. Going a step further, we can also implement other asynchronous streaming APIs based on and , such as , , etc. Channel AsyncTask Async Await all race The full source code can be found . here