First of all, this is my first non-dummy program using Go. Any recommendation will be appreciated.
Code description:
I want to retrieve all the information from an API where the information is being paginated. So I want to iterate through all the pages in order to get all the information.
This is what I did so far:
I have the these two functions:
func request(requestData *RequestData) []*ProjectsResponse {
client := &http.Client{
Timeout: time.Second * 10,
}
projects := []*ProjectsResponse{}
innerRequest(client, requestData.URL, projects)
return projects
}
func innerRequest(client *http.Client, URL string, projects []*ProjectsResponse) {
if URL == "" {
return
}
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
log.Printf("Request creation failed with error %s
", err)
}
req.Header.Add("Private-Token", os.Getenv("API_KEY"))
res, err := client.Do(req)
log.Printf("Executing request: %s", req.URL)
if err != nil {
log.Printf("The HTTP request failed with error %s
", err)
}
data, _ := ioutil.ReadAll(res.Body)
var response ProjectsResponse
err = json.Unmarshal(data, &response)
if err != nil {
log.Printf("Unmarshalling failed with error %s
", err)
}
projects = append(projects, &response)
pagingData := getPageInformation(res)
innerRequest(client, pagingData.NextLink, projects)
}
Undesired behavior:
The values in the projects []*ProjectsResponse
array are being appended on each iteration of the recursion, but when the recursion ends I get an empty array list. So, somehow after the innerRequests
ends, in the project
variable inside the request
method I get nothing.
Hope somebody and spot my mistake. Thanks in advance.
I'm guessing that all of your project objects are scoped to the function so they no longer exist when the function ends. I don't think you need your projects to exist before you call innerRequest, so you should probably just have that method return the projects. I think something like this should work...
func innerRequest(client *http.Client, URL string) []*ProjectsResponse {
if URL == "" {
return nil
}
//More code...
pagingData := getPageInformation(res)
return append([]*ProjectsResponse{&response}, innerRequest(client, pagingData.NextLink)...)
}
The confusion lies in the way a slice
is handled in Go. Here is the in-depth explanation, but I will abbreviate.
The common misconception is that the slice
you pass around is a reference to the slice
, which is false. The actual variable you operate on when you handle a slice is known as a slice header. This is a simple struct
with a reference to the underlying array under the covers and follows Go's pass by value rules, i.e. it is copied when passed to a function. Thus, if it is not returned, you won't have the updated header.
Returning data from recursion follows a straightforward pattern. Here is a basic example. I'm also including a version that doesn't require a return, but operates on the slice as a reference, which will modify the original. (Note: Passing around slice pointers is generally not considered idiomatic Go)
Playground link: https://play.golang.org/p/v5XeYpH1VlF
// correct way to return from recursion
func addReturn(s []int) []int {
if finalCondition(s) {
return s
}
s = append(s, 1)
return addReturn(s)
}
// using a reference
func addReference(s *[]int) {
if finalCondition(*s) {
return
}
*s = append(*s, 1)
addReference(s)
}
// whatever terminates the recursion
func finalCondition(s []int) bool {
if len(s) >= 10 {
return true
}
return false
}