code: func (t *Transaction) Exec() error {
  	// Return the connection to the pool when we are done
  	defer func() {
  		_ = t.conn.Close()
  	}()
  
  	// If the transaction had an error from a previous command, return it
  	// and don't continue
  	if t.err != nil {
  		return t.err
  	}
  
  	if len(t.actions) == 1 && len(t.watching) == 0 {
  		// If there is only one command and no keys being watched, no need to use
  		// MULTI/EXEC
  		a := t.actions[0]
  		reply, err := t.doAction(a)
  		if err != nil {
  			return err
  		}
  		if a.handler != nil {
  			if err := a.handler(reply); err != nil {
  				return err
  			}
  		}
  	} else {
  		// Send all the commands and scripts at once using MULTI/EXEC
  		if err := t.conn.Send("MULTI"); err != nil {
  			return err
  		}
  		for _, a := range t.actions {
  			if err := t.sendAction(a); err != nil {
  				return err
  			}
  		}
  		// Invoke redis driver to execute the transaction
  		replies, err := redis.Values(t.conn.Do("EXEC"))
  		if err != nil {
  			if err == redis.ErrNil && len(t.watching) > 0 {
  				return WatchError{keys: t.watching}
  			}
  			return err
  		}
  		// Iterate through the replies, calling the corresponding handler functions
  		for i, reply := range replies {
  			a := t.actions[i]
  			if err, ok := reply.(error); ok {
  				return err
  			}
  			if a.handler != nil {
  				if err := a.handler(reply); err != nil {
  					return err
  				}
  			}
  		}
  	}
  	return nil
  }
docs: // Exec executes the transaction, sequentially sending each action and
  // calling all the action handlers with the corresponding replies.