Handling errors in Goroutines

He who fears death will never do anything worth of a man who is alive.

While working on the server for my side project fontkit i wanted to retrieve data from the database concurrently. This seems like a trivial thing to do in go with goroutines but the problem was i needed to handle errors too. So this is what i came up with

NOTE: parts of original code cut for brevity.

db, err := sql.Open()
if err !=  nil {
    log.Print(err)
}

func QueryDatabase(son string) (Family, error) {
    var wg sync.WaitGroup
    var family Family

    // errChan will receive an error and once it does it returns it on the function
    errChan :=  make(chan  error)
    // sends a signal that the goroutines are done so the function can return
    done :=  make(chan  bool)
    // NOTE: See bottom of code for select statement using errChan and done

    wg.Add(1)
    go  func(wg *sync.WaitGroup) {
        defer wg.Done()

        query := fmt.Sprintf("SELECT father, mother FROM family WHERE son='%v'", son)
        stmt, err := db.Prepare(query)
        if err !=  nil {
            errChan <- err
            return
        }
    
        stmt, err := db.Prepare(query)
        if err !=  nil {
            errChan <- err
            return
        }

        row := stmt.QueryRow()
        if err = row.Scan(&family.father, &family.mother); err !=  nil {
            if err == sql.ErrNoRows {
                return
            }
            errChan <- err
            return
        }
    }(&wg)

  

    wg.Add(1)
    go  func(wg *sync.WaitGroup) {
        defer wg.Done()

        query := fmt.Sprintf("SELECT uncle, aunty FROM extended_family WHERE nephew='%v'", son)

        stmt, err := db.Prepare(query)
        if err !=  nil {
            errChan <- err
            return
        }
    
        stmt, err := db.Prepare(query)
        if err !=  nil {
            errChan <- err
            return
        }
    
        row := stmt.QueryRow()
        if err = row.Scan(&family.uncle, &family.aunt); err !=  nil {
            if err == sql.ErrNoRows {
                return
            }
            errChan <- err
            return
        }
    }(&wg)

    // this goroutine is created so the waitgroup doesn't block the execution of the select statement to wait for errors
    go  func(wg *sync.WaitGroup) {
        wg.Wait()
        close(done)
    }(&wg)

    select {
        case  <-done:
        return coll, nil
    case err :=  <-errChan:
        return coll, err
    }

}

You probably noticed the return statements after the send to a channel like this:

errChan <- err
return

This is because the code defer wg.Done() runs when a function has finished execution and returns. Without the return statement after the send to a channel i could be working with a nil value and that isn't good.

If you have anything to add, please comment below