Going Async with Channels and go routines

One of the early Feedback received for Social Paster has been that people wanted Login with Facebook functionality.

And Heeding the feedback, I have started working on it. Login with Facebook functionality required me to integrate Facebook’s javascript SDK. After user logs in and authenticates the app, I receive a short lived access token and Facebook user id.

Now, to create user account for my application, I need their emails and name and also, rather then a short lived token, I need a long lived token, so that I can post on their profiles. To achieve these objectives I need to make 2 separate API calls to server and store their results in DB.

Once all is done, the user moves on to the next page.

Making 2 API calls from server to Facebook’s API is slow, and user needs to wait for the whole process to complete. I need to make that faster.

I decided on using go routines. They provide async behaviour, but I soon realised that there was a dependency between 2 API calls, I could not move forward, if either of them failed and I wanted them to return values. Go routines were not fitting the bill.

Enter channels. Using channels I could get methods to return values (write on channels), while maintaining async behaviour.

Lets see how we can do that
Lets first create two structs which we will use in our channels


type FacebookUserDetailResult struct{
	user *models.User
	facebookUserId string
	err error
}

type FacebookAccessTokenResult struct {
	accessToken string
	expires string
	err error
}

Now once, “Login with Facebook” button is clicked, the Facebook, after authentication calls login action, which looks like

func (c Facebook) Login(shortTermAccessToken, userId) revel.Result {	
   if len(userId) > 0 && len(shortTermAccessToken) > 0 {
	
		// we create 2 channels
                facebookUserDetailsChan := make(chan FacebookUserDetailResult)
		facebookAccessTokenChan := make(chan FacebookAccessTokenResult)
		
                // 2 go routines to fetch data
                go c.fetchUserDetails(shortTermAccessToken, facebookUserDetailsChan )
		go c.FetchLongTermToken(shortTermAccessToken, facebookAccessTokenChan)

                facebookUserDetails := <- facebookUserDetailsChan
		println(facebookUserDetails.user.Email)
		println(facebookUserDetails.facebookUserId)
		facebookAccessTokenResult := <- facebookAccessTokenChan
		println(facebookAccessTokenResult.accessToken)
		println(facebookAccessTokenResult.expires)
                //normal execution of rest of the code.
		c.saveUserDetailsInDatabase(facebookUserDetails, facebookAccessTokenResult)
	}
	return c.RenderText("Success");
}

So we created 2 channels and passed them in go routines, and later we access the values in println statements.

Wow! How does that work? Won’t println statements be executed before go routines finish?

If you see we have facebookUserDetails := <- facebookUserDetailsChan Here, we are reading values of the channels and these Println statements will implicitly wait for values in channel.

Now to the implementation of one of the go routines, lets take a look at c.fetchUserDetails()

func (c Facebook) fetchUserDetails(shortTermAccessToken string,  facebookUserDetailsChan chan FacebookUserDetailResult) {
	defer close(facebookUserDetailsChan)

	var httpClient *http.Client
	httpClient = http.DefaultClient
	resp, err := httpClient.Get("https://graph.facebook.com/me?access_token=" + shortTermAccessToken)
	facebookUserDetailResult := new(FacebookUserDetailResult)

	if err != nil {
		facebookUserDetailResult.user = nil
		facebookUserDetailResult.facebookUserId = &quot;&quot;
		facebookUserDetailResult.err = err
		facebookUserDetailsChan <- *facebookUserDetailResult
		return
	}

	defer resp.Body.Close()
	contents, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		facebookUserDetailResult.user = nil
		facebookUserDetailResult.facebookUserId = &quot;&quot;
		facebookUserDetailResult.err = err
		facebookUserDetailsChan <- *facebookUserDetailResult
		return
	}
       ...
         Some more json parsing code
       ....
	user := &models.User{FirstName:firstName, LastName:lastName, Email:email}


	facebookUserDetailResult.user = user
	facebookUserDetailResult.facebookUserId = facebookId
	facebookUserDetailsChan <- *facebookUserDetailResult
}

In this function, we make an api call to graph api and put the values on the channel, once the values are in channel, they are printed.

Let me know, If you see any problems or have any better ways!

Update: Removed wait group since it was not needed. See comments.

~~ Whizdumb ~~
Email : sachin.xpert@gmail.com

2 responses

  1. The wg.Wait() is useless: you never call wg.Add() or wg.Done() and wg is not being passed into the go routines. Your code will work without the WaitGroup code.

Comments are closed.