You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何封装go-retry的RetryFunc以适配任意GitHub API调用

问题:适配任意GitHub API的重试封装

我在使用go-retry包对GitHub API调用实现重试逻辑,该包的RetryFunc仅接受func(ctx context.Context) error类型的闭包。但我有大量参数、返回值各不相同的GitHub API需要添加重试,目前每次都要重复编写嵌套闭包和重试判断代码,希望改写retryWithExpBackoff,让它能适配任意GitHub API调用。

原示例代码:

import (
    "context"
    "fmt"
    "net/http"
    "strings"
    "time"

    "github.com/google/go-github/v61/github"
    "github.com/sethvargo/go-retry"
)

func retryDecision(resp *github.Response) error {
    if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) {
        return retry.RetryableError(fmt.Errorf("unexpected HTTP status %s", resp.Status))
    }
    return nil
}

func retryWithExpBackoff(ctx context.Context, f func(ctx context.Context) error) error {
    b := retry.NewExponential(1 * time.Second)
    b = retry.WithMaxRetries(5, b)
    b = retry.WithMaxDuration(5*time.Second, b)
    return retry.Do(ctx, b, f)
}

func NewGitHubClient(ctx context.Context, token, repo string) (*github.Client, error) {
    r := strings.Split(repo, "/")
    client := github.NewClient(nil).WithAuthToken(token)

    if err := retryWithExpBackoff(ctx, func(ctx context.Context) error {
        _, resp, err := client.Repositories.Get(ctx, r[0], r[1])
        if retryErr := retryDecision(resp); retryErr != nil {
            return retryErr
        }
        return err
    }); err != nil {
        return nil, err
    }

    return client, nil
}

重复编写的冗余代码块:

if err := retryWithExpBackoff(ctx, func(ctx context.Context) error {
    // <-------- 需要重试的API --------->
    //_, resp, err := client.Repositories.Get(ctx, r[0], r[1])
    if retryErr := retryDecision(resp); retryErr != nil {
        return retryErr
    }
    return err
}); err != nil {
    return nil, err
}

解决方案:用泛型封装通用重试逻辑

利用Go 1.18+的泛型特性,我们可以封装一个适配GitHub API通用签名(返回(结果, *github.Response, error))的重试函数,彻底消除重复代码:

1. 改写通用重试函数

// retryGitHubAPI 通用重试函数,支持所有返回 (T, *github.Response, error) 的GitHub API
func retryGitHubAPI[T any](ctx context.Context, apiFunc func(ctx context.Context) (T, *github.Response, error)) (T, error) {
    var result T
    err := retry.Do(
        ctx,
        retry.WithMaxDuration(5*time.Second, retry.WithMaxRetries(5, retry.NewExponential(1*time.Second))),
        func(ctx context.Context) error {
            var resp *github.Response
            result, resp, err = apiFunc(ctx)
            // 复用原有的重试判断逻辑
            if retryErr := retryDecision(resp); retryErr != nil {
                return retryErr
            }
            return err
        },
    )
    return result, err
}

2. 简化API调用的重试实现

以原有的NewGitHubClient为例,改写后无需重复嵌套闭包:

func NewGitHubClient(ctx context.Context, token, repo string) (*github.Client, error) {
    r := strings.Split(repo, "/")
    client := github.NewClient(nil).WithAuthToken(token)

    // 直接传入API调用逻辑,重试逻辑完全封装在retryGitHubAPI中
    _, err := retryGitHubAPI(ctx, func(ctx context.Context) (*github.Repository, *github.Response, error) {
        return client.Repositories.Get(ctx, r[0], r[1])
    })
    if err != nil {
        return nil, err
    }

    return client, nil
}

3. 适配其他GitHub API的示例

比如调用创建Issue的API,只需传入对应API的闭包即可:

func createGitHubIssue(ctx context.Context, client *github.Client, owner, repo string, issueReq *github.IssueRequest) (*github.Issue, error) {
    return retryGitHubAPI(ctx, func(ctx context.Context) (*github.Issue, *github.Response, error) {
        return client.Issues.Create(ctx, owner, repo, issueReq)
    })
}

补充说明

  • 该泛型函数适配绝大多数GitHub API的返回签名,如果遇到少数返回值结构不同的API,可以新增对应签名的泛型重载函数
  • 重试策略(指数退避、最大重试次数、超时)可以根据需求在retryGitHubAPI中统一调整,无需逐个修改API调用处

内容的提问来源于stack exchange,提问作者Inian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:20:16