Often the best way to learn a new language is to implement something you know

with it. Let’s take a look at some common async Javascript patterns, and how

you’d implement them in Go.

Callbacks

You can certainly implement callbacks in Go! Functions are first class

citizens. Here we make a HTTP request and hit the callback with the response

body or an error.

func makeRequest ( method string , url string , cb func ( error , io . ReadCloser )) { req , _ := http . NewRequest ( method , url , nil ) resp , err := http . DefaultClient . Do ( req ) if err != nil { cb ( err , nil ) } else { cb ( err , resp . Body ) } } func main () { makeRequest ( "GET" , "http://ipinfo.io/json" , func ( err error , body io . ReadCloser ) { defer body . Close () io . Copy ( os . Stdout , body ) }) }

Go uses callbacks in the standard library in some places – time.AfterFunc ,

http.HandlerFunc . Other places callbacks are mostly used to allow you to

specify what function or action you want to take, in interaction with a useful

piece of code – filepath.Walk , strings.LastIndexFunc , etc.

Most of the same problems with callbacks apply to Go as Javascript, namely, you

can forget to call a callback, or you can call it more than once. At least you

can’t try to callback that is undefined in Go, since the compiler will not

let you do this.

But callbacks aren’t idiomatic. Javascript needs callbacks because there’s

only a single thread of execution, processing every request on your server

sequentially. If you "wait"/"block" for something to finish (e.g. by calling

fs.readFileSync or any of the Sync methods, every other request on your

server has to wait until you’re done. I’m simplifying, but you have to have

callbacks in Javascript so everything else (other incoming HTTP requests, for

example) have a chance to run.

Go does not have this same limitation. A Javascript thread can only run on one

CPU; Go can use all of the CPU’s on your computer, so multiple threads can

do work at the same time, even if all of them are making "blocking" function

calls, like HTTP requests, database queries, or reading or writing files.

Furthermore, even if you only had one CPU, multiple Go threads would run

without having IO "block" the scheduler, even though methods like http.Get

have a synchronous interface. How does this work? Deep down in code like

net/fd_unix.go , the socket code calls syscall.Write, which (when you follow

it far enough) calls runtime.entersyscall , which signals to the scheduler

that it won’t have anything to do for a while, and other work should

proceed.

For that reason you probably don’t want to use callbacks for asynchronous code.

.then / .catch / .finally

This is the other common approach to async operations in Javascript.

Fortunately most API’s in Go are blocking, so you can just do one thing and

then the other thing. Say you want to execute the following async actions:

var changeEmail = function ( userId , newEmail ) { return checkEmailAgainstBlacklist ( newEmail ). then ( function () { return getUser ( userId ); }). then ( function ( user ) { user . email = newEmail ; return user . save (); }). catch ( function ( err ) { console . error ( err ); throw err ; }); }

In Go, you’d just do each action in turn and wait for them to finish:

func changeEmail ( userId string , newEmail string ) ( User , error ) { err := checkEmailAgainstBlacklist ( newEmail ) if err != nil { return nil , err } user , err := getUser ( userId ) if err != nil { return nil , err } user . Email = newEmail return user . Save () }

.finally is usually implemented with the defer keyword; you can at any time

defer code to run at the point the function terminates.

resp , err := http . Get ( "http://ipinfo.io/json" ) // Note Close() will panic if err is non-nil, you still have to check err checkError ( err ) defer resp . Body . Close () io . Copy ( os . Stdout , resp . Body )

Promise.try

Just ignore the error response, or handle it; there’s no real difference

between sync and async errors, besides calling panic , which your code

shouldn’t be doing.

Promise.join

Doing multiple things at once in Javascript is pretty easy with Promises.

Here we dispatch two database queries at once, and handle the results in one

function.

var checkUserOwnsPickup = function ( userId , pickupId ) { return Promise . join ( getUser ( userId ), getPickup ( pickupId ) ). spread ( function ( user , pickup ) { return pickup . userId === user . id ; }) }

Unfortunately doing this in Go is a little more complicated. Let’s start with

a slightly easier case – doing something where we don’t care about the result,

whether it errors or not. You can put each in a goroutine and they’ll execute

in the background.

func subscribeUser ( user User ) { go func () { sendWelcomeEmail ( user . Email ) }() go func () { metrics . Increment ( ' user . signup ' ) }() }

The easiest way to pass the results back is to declare the variables in the

outer scope and assign them in a goroutine.

func main () { var user models . User var userErr error var pickup models . Pickup var pickupErr error var wg sync . WaitGroup wg . Add ( 2 ) go func () { user , userErr = getUser ( userId ) wg . Done () }() go func () { pickup , pickupErr = getPickup ( pickupId ) wg . Done () }() wg . Wait () // Error checking omitted fmt . Println ( user . Id ) fmt . Println ( pickup . Id ) }

More verbose than Promise.join , but the lack of generics primitives makes a

shorter solution tough. You can gain more control, and early terminate by using

channels, but it’s more verbose to do so.

Promise.map

Do the same as the Go version of Promise.join above, and then write a for

loop to loop over the resolved values. This might be a little trickier because

you need to specify the types of everything, and you can’t create arrays with

differing types. If you want a given concurrency value, there are some neat

code examples in this post on Pipelines.

Checking for races

One of the strengths of Go is the race detector, which detects whether

two threads can attempt to read/write the same variable in different orders

without synchronizing first. It’s slightly slower than normal, but I highly

encourage you to run your test suite with the race detector on. Here’s our

make target:

test: go test -race ./... -timeout 2s

Once it found a defect in our code that turned out to be a standard library

error! I highly recommend enabling the race detector.

The equivalent in Javascript would be code that varies the event loop each

callback runs in, and checks that you still get the same results.

Thanks

I hope this helps! I wrote this to be the guide I wish I had when I was getting

started figuring out how to program concurrently in Go.

Thanks to Kyle Conroy, Alan Shreve, Chris

Goddard, @rf, and members of the #go-nuts IRC channel for

answering my silly questions.

Liked what you read? I am available for hire.