























@@ -43,6 +43,11 @@ type GitHubJsonOptions = {
4343timeoutMs?: number;
4444};
454546+type GitHubBodyReadOptions = {
47+signal?: AbortSignal;
48+timeoutPromise?: Promise<never>;
49+};
50+4651export function parseRepoArg(args: string[]): string | null {
4752for (let i = 0; i < args.length; i += 1) {
4853const arg = args[i];
@@ -174,11 +179,11 @@ function createAppJwt(appId: string, privateKeyPem: string) {
174179async function withGitHubFetchTimeout<T>(
175180label: string,
176181timeoutMs: number,
177-run: (signal: AbortSignal) => Promise<T>,
182+run: (signal: AbortSignal, timeoutPromise: Promise<never>) => Promise<T>,
178183): Promise<T> {
179184const controller = new AbortController();
180185let timeout: ReturnType<typeof setTimeout> | undefined;
181-const timeoutPromise = new Promise<T>((_resolve, reject) => {
186+const timeoutPromise = new Promise<never>((_resolve, reject) => {
182187timeout = setTimeout(() => {
183188const error = new Error(`${label} exceeded timeout of ${timeoutMs}ms`);
184189reject(error);
@@ -194,9 +199,35 @@ async function withGitHubFetchTimeout<T>(
194199}
195200}
196201202+function cancelReaderSoon(reader: ReadableStreamDefaultReader<Uint8Array>): void {
203+void Promise.resolve()
204+.then(() => reader.cancel())
205+.catch(() => undefined);
206+}
207+208+async function readGitHubErrorChunk(
209+reader: ReadableStreamDefaultReader<Uint8Array>,
210+timeoutPromise: Promise<never> | undefined,
211+markCanceled: () => void,
212+): Promise<ReadableStreamReadResult<Uint8Array>> {
213+const read = reader.read();
214+if (!timeoutPromise) {
215+return await read;
216+}
217+return await Promise.race([
218+read,
219+timeoutPromise.catch((error: unknown) => {
220+markCanceled();
221+cancelReaderSoon(reader);
222+throw error;
223+}),
224+]);
225+}
226+197227export async function readBoundedGitHubErrorText(
198228response: Response,
199229maxChars = GITHUB_ERROR_BODY_MAX_CHARS,
230+options: Pick<GitHubBodyReadOptions, "timeoutPromise"> = {},
200231): Promise<string> {
201232if (!response.body) {
202233return "";
@@ -206,10 +237,13 @@ export async function readBoundedGitHubErrorText(
206237const decoder = new TextDecoder();
207238let text = "";
208239let truncated = false;
240+let canceled = false;
209241210242try {
211243while (text.length <= maxChars) {
212-const { done, value } = await reader.read();
244+const { done, value } = await readGitHubErrorChunk(reader, options.timeoutPromise, () => {
245+canceled = true;
246+});
213247if (done) {
214248text += decoder.decode();
215249break;
@@ -225,7 +259,7 @@ export async function readBoundedGitHubErrorText(
225259} finally {
226260if (truncated) {
227261await reader.cancel().catch(() => undefined);
228-} else {
262+} else if (!canceled) {
229263reader.releaseLock();
230264}
231265}
@@ -236,12 +270,15 @@ export async function readBoundedGitHubErrorText(
236270export async function readBoundedGitHubJson<T>(
237271response: Response,
238272maxBytes = GITHUB_JSON_BODY_MAX_BYTES,
273+options: GitHubBodyReadOptions = {},
239274): Promise<T> {
240275const text = await readBoundedResponseText(response, "GitHub API", maxBytes, {
241276createTooLargeError: (message) =>
242277Object.assign(new Error(message), {
243278code: "ETOOBIG",
244279}),
280+signal: options.signal,
281+timeoutPromise: options.timeoutPromise,
245282});
246283return JSON.parse(text) as T;
247284}
@@ -260,7 +297,7 @@ export async function githubJson<T>(
260297return await withGitHubFetchTimeout(
261298`GitHub API ${init?.method ?? "GET"} ${path}`,
262299timeoutMs,
263-async (signal) => {
300+async (signal, timeoutPromise) => {
264301const response = await fetchImpl(`https://api.github.com${path}`, {
265302method: init?.method ?? "GET",
266303headers: {
@@ -275,11 +312,11 @@ export async function githubJson<T>(
275312});
276313277314if (!response.ok) {
278-const text = await readBoundedGitHubErrorText(response);
315+const text = await readBoundedGitHubErrorText(response, undefined, { timeoutPromise });
279316fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
280317}
281318282-return await readBoundedGitHubJson<T>(response);
319+return await readBoundedGitHubJson<T>(response, undefined, { signal, timeoutPromise });
283320},
284321);
285322}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。