# 使用 REST API 和 JavaScript 编写脚本

使用 Octokit.js SDK 编写脚本，以便与 REST API 交互。

## 关于 Octokit.js

如果要使用 JavaScript 编写脚本以与 GitHubREST API 交互， GitHub 建议使用 Octokit.js SDK。 Octokit.js 由 GitHub 维护。 SDK 实现了最佳做法，可让你更轻松地通过 JavaScript 与 REST API 进行交互。 Octokit.js 适用于所有新式浏览器、Node.js 和 Deno。 有关 Octokit.js 的详细信息，请参阅 [Octokit.js 自述文件](https://github.com/octokit/octokit.js/#readme)。

## 先决条件

本指南假定你熟悉 JavaScript 和 GitHub REST API。 有关 REST API 的详细信息，请参阅 [REST API 入门](/zh/enterprise-server@3.22/rest/using-the-rest-api/getting-started-with-the-rest-api)。

必须安装和导入 `octokit`，才能使用 Octokit.js 库。 本指南使用符合 ES6 的导入语句。 有关不同安装和导入方法的详细信息，请参阅 [Octokit.js 自述文件的用法部分](https://github.com/octokit/octokit.js/#usage)。

## 实例化和身份验证

> \[!WARNING]
> 将身份验证凭据视为密码。
>
> 若要保护凭据安全，可以将凭据存储为机密，并通过 GitHub Actions运行脚本。 有关详细信息，请参阅“[在 GitHub Actions 中使用机密](/zh/enterprise-server@3.22/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets)”。

> 如果，请考虑使用另一个 CLI 服务安全地存储凭据。

### 使用personal access token进行身份验证

如果要使用 GitHub REST API 进行个人使用，可以创建一个 personal access token。 有关创建personal access token的详细信息，请参阅[管理个人访问令牌](/zh/enterprise-server@3.22/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)。

首先，从 `Octokit` 导入 `octokit`。 然后，在创建 personal access token 的实例时，传入你的 `Octokit`。 在下面的示例中，将 `YOUR-TOKEN` 替换为对 personal access token 的引用。 用`HOSTNAME`的名称替换你的 GitHub Enterprise Server 实例。

```javascript copy
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: 'YOUR-TOKEN',
});
```

### 使用GitHub App进行身份验证

如果要代表组织或其他用户使用 API， GitHub 建议使用 GitHub App。 如果某个终结点可供 GitHub Apps 使用，该终结点的 REST 参考文档会注明需要哪种类型的 GitHub App 令牌。 有关详细信息，请参阅 [注册GitHub应用](/zh/enterprise-server@3.22/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) 和 [关于使用 GitHub 应用进行身份验证](/zh/enterprise-server@3.22/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app)。

不是从 `Octokit` 导入 `octokit`，而是导入 `App`。 在以下示例中，将 `APP_ID` 替换为对应用 ID 的引用。 将 `PRIVATE_KEY` 替换为对应用私钥的引用。 将 `INSTALLATION_ID` 替换为要代表其进行身份验证的应用的安装 ID。 可以在应用的设置页面上找到应用的 ID 并生成私钥。 有关详细信息，请参阅“[管理GitHub应用的私钥](/zh/enterprise-server@3.22/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps)”。 可以使用 `GET /users/{username}/installation`、`GET /repos/{owner}/{repo}/installation` 或 `GET /orgs/{org}/installation` 终结点获取安装 ID。 有关详细信息，请参阅 [GitHub Apps 的 REST API 终结点](/zh/enterprise-server@3.22/rest/apps/apps)。 用`HOSTNAME`的名称替换你的 GitHub Enterprise Server 实例。

```javascript copy
import { App } from "octokit";

const app = new App({
  appId: APP_ID,
  privateKey: PRIVATE_KEY,
  Octokit: Octokit.defaults({
    baseUrl: "http(s)://HOSTNAME/api/v3",
  }),
});

const octokit = await app.getInstallationOctokit(INSTALLATION_ID);
```

### 在 GitHub Actions 中进行身份验证

如果要在工作流中使用 GitHub Actions API， GitHub 建议使用内置 `GITHUB_TOKEN` 身份验证，而不是创建令牌。 可以使用 `GITHUB_TOKEN` 密钥向 `permissions` 授予权限。 有关 `GITHUB_TOKEN` 的详细信息，请参阅 [GITHUB\_TOKEN](/zh/enterprise-server@3.22/actions/concepts/security/github_token)。

如果工作流需要访问工作流存储库之外的资源，则无法使用 `GITHUB_TOKEN`。 在这种情况下，请将凭据存储为机密，并将以下示例中的 `GITHUB_TOKEN` 替换为机密的名称。 有关机密的详细信息，请参阅 [在 GitHub Actions 中使用机密](/zh/enterprise-server@3.22/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets)。

如果使用 `run` 关键字在工作流中 GitHub Actions 执行 JavaScript 脚本，则可以将该值 `GITHUB_TOKEN` 存储为环境变量。 脚本可以作为 `process.env.VARIABLE_NAME` 访问环境变量。

例如，此工作流步骤将 `GITHUB_TOKEN` 存储在名为 `TOKEN` 的环境变量中：

```yaml
- name: Run script
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    node .github/actions-scripts/use-the-api.mjs
```

工作流运行的脚本使用 `process.env.TOKEN` 进行身份验证：

```javascript copy
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: process.env.TOKEN,
});
```

### 无需身份验证即可实例化

可以在不进行身份验证的情况下使用 REST API，但速率限制较低，并且无法使用某些终结点。 若要创建不进行身份验证的 `Octokit` 实例，请不要传递 `auth` 参数。 将基 URL 设置为 `http(s)://HOSTNAME/api/v3`. 用`[hostname]`的名称替换你的 GitHub Enterprise Server 实例。

```javascript copy
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
);
```

## 发出请求

Octokit 支持多种请求方式。 如果知道终结点的 HTTP 谓词和路径，则可以使用 `request` 方法发出请求。 如果要利用 IDE 中的自动完成和键入功能，可以使用 `rest` 方法。 对于分页终结点，可以使用 `paginate` 方法来请求多页数据。

### 使用 `request` 方法发出请求

若要使用 `request` 方法发出请求，请将 HTTP 方法和路径作为第一个参数传递。 将对象中的任何主体、查询或路径参数作为第二个参数传递。 例如，向 `GET` 发出 `/repos/{owner}/{repo}/issues` 请求并传递 `owner`、`repo` 和 `per_page` 参数：

```javascript copy
await octokit.request("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 2
});
```

`request` 方法会自动传递 `Accept: application/vnd.github+json` 标头。 若要传递其他标头或不同的 `Accept` 标头，请将 `headers` 属性添加到作为第二个参数传递的对象。
`headers` 属性的值是将标头名称作为键并将标头值作为值的对象。 例如，发送值为 `content-type` 的 `text/plain` 标头和值为 `x-github-api-version` 的 `2026-03-10` 标头：

```javascript copy
await octokit.request("POST /markdown/raw", {
  text: "Hello **world**",
  headers: {
    "content-type": "text/plain",
    "x-github-api-version": "2026-03-10",
  },
});
```

### 使用 `rest` 接口方法发出请求

每个 REST API 终结点在 Octokit 中都有一个关联的 `rest` 终结点方法。 为方便起见，这些方法通常会在 IDE 中自动完成。 可以将任何参数作为对象传递给该方法。

```javascript copy
await octokit.rest.issues.listForRepo({
  owner: "github",
  repo: "docs",
  per_page: 2
});
```

此外，如果使用类型化语言（如 TypeScript），则可以导入用于这些方法的类型。 有关详细信息，请参阅 [plugin-rest-endpoint-methods.js 自述文件中的 TypeScript 部分](https://github.com/octokit/plugin-rest-endpoint-methods.js/#typescript)。

### 发出分页请求

如果终结点已分页，并且你想要提取多页结果，则可以使用 `paginate` 方法。
`paginate` 将提取下一个结果页到最后一页的结果，然后将所有结果作为单个数组返回。 一些终结点将分页结果作为对象中的数组返回，而不是以数组的形式返回分页结果。
`paginate` 始终返回项的数组，即使原始结果为一个对象。

例如，以下示例从 `github/docs` 存储库中获取所有问题。 虽然它一次请求 100 个问题，但函数在到达最后一页数据之前不会返回。

```javascript copy
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
});
```

`paginate` 方法接受可选的 map 函数，可用于仅从响应中收集所需的数据。 这将减少脚本的内存使用量。 map 函数可以采用第二个参数 `done`，可以调用该参数在到达最后一页之前结束分页。 这样，便可以提取页面的子集。 例如，以下示例继续提取结果，直到返回标题中包含“test”的问题。 对于返回的数据页面，仅存储问题标题和作者。

```javascript copy
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
},
    (response, done) => response.data.map((issue) => {
    if (issue.title.includes("test")) {
      done()
    }
    return ({title: issue.title, author: issue.user.login})
  })
);
```

可以使用 `octokit.paginate.iterator()` 一次循环访问一个页面，而不是一次提取所有结果。 例如，以下示例一次提取一页结果，并在提取下一页之前处理页面中的每个对象。 到达标题中包含“test”的问题后，脚本将停止迭代，并返回已处理的每个对象的问题标题和问题作者。 迭代器是提取分页数据的内存效率最高的方法。

```javascript copy
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
});

let issueData = []
let breakLoop = false
for await (const {data} of iterator) {
  if (breakLoop) break
  for (const issue of data) {
    if (issue.title.includes("test")) {
      breakLoop = true
      break
    } else {
      issueData = [...issueData, {title: issue.title, author: issue.user.login}];
    }
  }
}
```

也可以将 `paginate` 方法与 `rest` 终结点方法一起使用。 将 `rest` 终结点方法作为第一个参数传递。 将任何参数作为第二个参数传递。

```javascript copy
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
});
```

有关分页的详细信息，请参阅 [在 REST API 中使用分页](/zh/enterprise-server@3.22/rest/using-the-rest-api/using-pagination-in-the-rest-api)。

## 捕获错误

### 捕获所有错误

有时， GitHub REST API 将返回错误。 例如，如果访问令牌已过期或省略了必需的参数，则会收到错误。 Octokit .js 在收到除 `400 Bad Request`、`401 Unauthorized`、`403 Forbidden`、`404 Not Found` 和 `422 Unprocessable Entity` 以外的错误时，会自动重试请求。 如果在重试后仍发生 API 错误，Octokit.js 会引发一个错误，其中包含响应的 HTTP 状态代码 (`response.status`) 和响应标头 (`response.headers`)。 应在代码中处理这些错误。 例如，可以使用 try/catch 块来捕获错误：

```javascript copy
let filesChanged = []

try {
  const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
    owner: "github",
    repo: "docs",
    pull_number: 22809,
    per_page: 100,
    headers: {
      "x-github-api-version": "2026-03-10",
    },
  });

  for await (const {data} of iterator) {
    filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
  }
} catch (error) {
  if (error.response) {
    console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
  }
  console.error(error)
}
```

### 处理预期的错误代码

有时， GitHub 使用 4xx 状态代码来指示非错误响应。 如果您正在使用的终结点这样做，则可以为特定错误添加额外的处理措施。 例如，如果存储库未加星标，则 `GET /user/starred/{owner}/{repo}` 终结点将返回 `404`。 以下示例使用 `404` 响应来指示存储库未加星标；所有其他错误代码都被视为错误。

```javascript copy
try {
  await octokit.request("GET /user/starred/{owner}/{repo}", {
    owner: "github",
    repo: "docs",
    headers: {
      "x-github-api-version": "2026-03-10",
    },
  });

  console.log(`The repository is starred by me`);

} catch (error) {
  if (error.status === 404) {
    console.log(`The repository is not starred by me`);
  } else {
    console.error(`An error occurred while checking if the repository is starred: ${error?.response?.data?.message}`);
  }
}
```

### 处理速率限制错误

如果收到速率限制错误，可能需要在等待后重试请求。 当您受到速率限制时，GitHub 会返回一个 `403 Forbidden` 错误，且 `x-ratelimit-remaining` 响应标头的值将为 `"0"`。 响应标头将包含一个 `x-ratelimit-reset` 标头，该标头告知当前速率限制窗口重置的时间（以 UTC 纪元秒为单位）。 可以在 `x-ratelimit-reset` 指定的时间后重试请求。

```javascript copy
async function requestRetry(route, parameters) {
  try {
    const response = await octokit.request(route, parameters);
    return response
  } catch (error) {
    if (error.response && error.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
      const resetTimeEpochSeconds = error.response.headers['x-ratelimit-reset'];
      const currentTimeEpochSeconds = Math.floor(Date.now() / 1000);
      const secondsToWait = resetTimeEpochSeconds - currentTimeEpochSeconds;
      console.log(`You have exceeded your rate limit. Retrying in ${secondsToWait} seconds.`);
      setTimeout(requestRetry, secondsToWait * 1000, route, parameters);
    } else {
      console.error(error);
    }
  }
}

const response = await requestRetry("GET /repos/{owner}/{repo}/issues", {
    owner: "github",
    repo: "docs",
    per_page: 2
  })
```

## 利用响应功能

`request` 方法返回一个承诺，如果请求成功，该承诺将解析为对象。 对象属性包括 `data`（终结点返回的响应正文）、`status`（HTTP 响应代码）、`url`（请求的 URL）和 `headers`（包含响应标头的对象）。 除非另外指定，否则响应正文会采用 JSON 格式。 某些终结点不返回响应正文；在这些情况下，将省略 `data` 属性。

```javascript copy
const response = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", {
  owner: "github",
  repo: "docs",
  issue_number: 11901,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
});

console.log(`The status of the response is: ${response.status}`)
console.log(`The request URL was: ${response.url}`)
console.log(`The x-ratelimit-remaining response header is: ${response.headers["x-ratelimit-remaining"]}`)
console.log(`The issue title is: ${response.data.title}`)
```

同样，`paginate` 方法返回一个承诺。 如果请求成功，承诺将解析为终结点返回的数据数组。 与 `request` 方法不同，`paginate` 方法不返回状态代码、URL 或标头。

```javascript copy
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2026-03-10",
  },
});

console.log(`${data.length} issues were returned`)
console.log(`The title of the first issue is: ${data[0].title}`)
```

## 示例脚本

下面是使用 Octokit.js 的完整示例脚本。 该脚本导入 `Octokit` 并创建新的 `Octokit` 实例。 如果您想使用 GitHub App 而不是 personal access token 进行身份验证，则应导入并实例化 `App`，而不是 `Octokit`。 有关详细信息，请参阅[使用 GitHub App 进行身份验证](#authenticating-with-a-github-app)。

`getChangedFiles` 函数获取为拉取请求更改的所有文件。
`commentIfDataFilesChanged` 函数调用 `getChangedFiles` 函数。 如果拉取请求更改的任何文件的文件路径中包含 `/data/`，则该函数将对拉取请求进行注释。

```javascript copy
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: 'YOUR-TOKEN',
});

async function getChangedFiles({owner, repo, pullNumber}) {
  let filesChanged = []

  try {
    const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
      owner: owner,
      repo: repo,
      pull_number: pullNumber,
      per_page: 100,
      headers: {
        "x-github-api-version": "2026-03-10",
      },
    });

    for await (const {data} of iterator) {
      filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
    }
  } catch (error) {
    if (error.response) {
      console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
    }
    console.error(error)
  }

  return filesChanged
}

async function commentIfDataFilesChanged({owner, repo, pullNumber}) {
  const changedFiles = await getChangedFiles({owner, repo, pullNumber});

  const filePathRegex = new RegExp(/\/data\//, "i");
  if (!changedFiles.some(fileName => filePathRegex.test(fileName))) {
    return;
  }

  try {
    const {data: comment} = await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
      owner: owner,
      repo: repo,
      issue_number: pullNumber,
      body: `It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.`,
      headers: {
        "x-github-api-version": "2026-03-10",
      },
    });

    return comment.html_url;
  } catch (error) {
    if (error.response) {
      console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
    }
    console.error(error)
  }
}

await commentIfDataFilesChanged({owner: "github", repo: "docs", pullNumber: 191});
```

## 后续步骤

* 若要详细了解 Octokit.js，请参阅 [Octokit.js 文档](https://github.com/octokit/octokit.js/#readme)。
* 对于一些现实生活中的示例，请查看 Docs 如何 GitHub 通过 [搜索 GitHub Docs 存储库](https://github.com/search?q=repo%3Agithub%2Fdocs%20path%3A.github%20octokit\&type=code)来使用 Octokit.js。