Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 611b275

Browse filesBrowse files
authored
fix(refactor): async await instead of Promise chain (#711)
* fail early, fail fast * improve * improve tests * AbortSignal should result in a status code 0 * minor change * streamline further * remove case * status 500 for abort error * add Dispatcher testcase for timeout * adapt requested changes
1 parent bc059c8 commit 611b275
Copy full SHA for 611b275

File tree

Expand file treeCollapse file tree

6 files changed

+267
-184
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+267
-184
lines changed

‎package-lock.json

Copy file name to clipboardExpand all lines: package-lock.json
+10Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

Copy file name to clipboardExpand all lines: package.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"prettier": "3.3.2",
4242
"semantic-release-plugin-update-version-in-files": "^1.0.0",
4343
"typescript": "^5.0.0",
44+
"undici": "^6.19.2",
4445
"vitest": "^2.0.0"
4546
},
4647
"release": {

‎src/fetch-wrapper.ts

Copy file name to clipboardExpand all lines: src/fetch-wrapper.ts
+116-124Lines changed: 116 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,10 @@
11
import { isPlainObject } from "./is-plain-object.js";
22
import { RequestError } from "@octokit/request-error";
3-
import type { EndpointInterface } from "@octokit/types";
3+
import type { EndpointInterface, OctokitResponse } from "@octokit/types";
44

5-
export default function fetchWrapper(
5+
export default async function fetchWrapper(
66
requestOptions: ReturnType<EndpointInterface>,
7-
) {
8-
const log =
9-
requestOptions.request && requestOptions.request.log
10-
? requestOptions.request.log
11-
: console;
12-
const parseSuccessResponseBody =
13-
requestOptions.request?.parseSuccessResponseBody !== false;
14-
15-
if (
16-
isPlainObject(requestOptions.body) ||
17-
Array.isArray(requestOptions.body)
18-
) {
19-
requestOptions.body = JSON.stringify(requestOptions.body);
20-
}
21-
22-
let headers: { [header: string]: string } = {};
23-
let status: number;
24-
let url: string;
25-
7+
): Promise<OctokitResponse<any>> {
268
const fetch: typeof globalThis.fetch =
279
requestOptions.request?.fetch || globalThis.fetch;
2810

@@ -32,109 +14,46 @@ export default function fetchWrapper(
3214
);
3315
}
3416

35-
return fetch(requestOptions.url, {
36-
method: requestOptions.method,
37-
body: requestOptions.body,
38-
redirect: requestOptions.request?.redirect,
39-
// Header values must be `string`
40-
headers: Object.fromEntries(
41-
Object.entries(requestOptions.headers).map(([name, value]) => [
42-
name,
43-
String(value),
44-
]),
45-
),
46-
signal: requestOptions.request?.signal,
47-
// duplex must be set if request.body is ReadableStream or Async Iterables.
48-
// See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
49-
...(requestOptions.body && { duplex: "half" }),
50-
})
51-
.then(async (response) => {
52-
url = response.url;
53-
status = response.status;
54-
55-
for (const keyAndValue of response.headers) {
56-
headers[keyAndValue[0]] = keyAndValue[1];
57-
}
58-
59-
if ("deprecation" in headers) {
60-
const matches =
61-
headers.link && headers.link.match(/<([^>]+)>; rel="deprecation"/);
62-
const deprecationLink = matches && matches.pop();
63-
log.warn(
64-
`[@octokit/request] "${requestOptions.method} ${
65-
requestOptions.url
66-
}" is deprecated. It is scheduled to be removed on ${headers.sunset}${
67-
deprecationLink ? `. See ${deprecationLink}` : ""
68-
}`,
69-
);
70-
}
71-
72-
if (status === 204 || status === 205) {
73-
return;
74-
}
75-
76-
// GitHub API returns 200 for HEAD requests
77-
if (requestOptions.method === "HEAD") {
78-
if (status < 400) {
79-
return;
80-
}
81-
82-
throw new RequestError(response.statusText, status, {
83-
response: {
84-
url,
85-
status,
86-
headers,
87-
data: undefined,
88-
},
89-
request: requestOptions,
90-
});
91-
}
92-
93-
if (status === 304) {
94-
throw new RequestError("Not modified", status, {
95-
response: {
96-
url,
97-
status,
98-
headers,
99-
data: await getResponseData(response),
100-
},
101-
request: requestOptions,
102-
});
103-
}
104-
105-
if (status >= 400) {
106-
const data = await getResponseData(response);
107-
108-
const error = new RequestError(toErrorMessage(data), status, {
109-
response: {
110-
url,
111-
status,
112-
headers,
113-
data,
114-
},
115-
request: requestOptions,
116-
});
17+
const log = requestOptions.request?.log || console;
18+
const parseSuccessResponseBody =
19+
requestOptions.request?.parseSuccessResponseBody !== false;
11720

21+
const body =
22+
isPlainObject(requestOptions.body) || Array.isArray(requestOptions.body)
23+
? JSON.stringify(requestOptions.body)
24+
: requestOptions.body;
25+
26+
// Header values must be `string`
27+
const requestHeaders = Object.fromEntries(
28+
Object.entries(requestOptions.headers).map(([name, value]) => [
29+
name,
30+
String(value),
31+
]),
32+
);
33+
34+
let fetchResponse: Response;
35+
36+
try {
37+
fetchResponse = await fetch(requestOptions.url, {
38+
method: requestOptions.method,
39+
body,
40+
redirect: requestOptions.request?.redirect,
41+
headers: requestHeaders,
42+
signal: requestOptions.request?.signal,
43+
// duplex must be set if request.body is ReadableStream or Async Iterables.
44+
// See https://fetch.spec.whatwg.org/#dom-requestinit-duplex.
45+
...(requestOptions.body && { duplex: "half" }),
46+
});
47+
// wrap fetch errors as RequestError if it is not a AbortError
48+
} catch (error) {
49+
let message = "Unknown Error";
50+
if (error instanceof Error) {
51+
if (error.name === "AbortError") {
52+
(error as RequestError).status = 500;
11853
throw error;
11954
}
12055

121-
return parseSuccessResponseBody
122-
? await getResponseData(response)
123-
: response.body;
124-
})
125-
.then((data) => {
126-
return {
127-
status,
128-
url,
129-
headers,
130-
data,
131-
};
132-
})
133-
.catch((error) => {
134-
if (error instanceof RequestError) throw error;
135-
else if (error.name === "AbortError") throw error;
136-
137-
let message = error.message;
56+
message = error.message;
13857

13958
// undici throws a TypeError for network errors
14059
// and puts the error message in `error.cause`
@@ -146,14 +65,87 @@ export default function fetchWrapper(
14665
message = error.cause;
14766
}
14867
}
68+
}
69+
70+
const requestError = new RequestError(message, 500, {
71+
request: requestOptions,
72+
});
73+
requestError.cause = error;
74+
75+
throw requestError;
76+
}
77+
78+
const status = fetchResponse.status;
79+
const url = fetchResponse.url;
80+
const responseHeaders: { [header: string]: string } = {};
81+
82+
for (const [key, value] of fetchResponse.headers) {
83+
responseHeaders[key] = value;
84+
}
85+
86+
const octokitResponse: OctokitResponse<any> = {
87+
url,
88+
status,
89+
headers: responseHeaders,
90+
data: "",
91+
};
92+
93+
if ("deprecation" in responseHeaders) {
94+
const matches =
95+
responseHeaders.link &&
96+
responseHeaders.link.match(/<([^>]+)>; rel="deprecation"/);
97+
const deprecationLink = matches && matches.pop();
98+
log.warn(
99+
`[@octokit/request] "${requestOptions.method} ${
100+
requestOptions.url
101+
}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${
102+
deprecationLink ? `. See ${deprecationLink}` : ""
103+
}`,
104+
);
105+
}
106+
107+
if (status === 204 || status === 205) {
108+
return octokitResponse;
109+
}
110+
111+
// GitHub API returns 200 for HEAD requests
112+
if (requestOptions.method === "HEAD") {
113+
if (status < 400) {
114+
return octokitResponse;
115+
}
116+
117+
throw new RequestError(fetchResponse.statusText, status, {
118+
response: octokitResponse,
119+
request: requestOptions,
120+
});
121+
}
122+
123+
if (status === 304) {
124+
octokitResponse.data = await getResponseData(fetchResponse);
125+
126+
throw new RequestError("Not modified", status, {
127+
response: octokitResponse,
128+
request: requestOptions,
129+
});
130+
}
131+
132+
if (status >= 400) {
133+
octokitResponse.data = await getResponseData(fetchResponse);
149134

150-
throw new RequestError(message, 500, {
151-
request: requestOptions,
152-
});
135+
throw new RequestError(toErrorMessage(octokitResponse.data), status, {
136+
response: octokitResponse,
137+
request: requestOptions,
153138
});
139+
}
140+
141+
octokitResponse.data = parseSuccessResponseBody
142+
? await getResponseData(fetchResponse)
143+
: fetchResponse.body;
144+
145+
return octokitResponse;
154146
}
155147

156-
async function getResponseData(response: Response) {
148+
async function getResponseData(response: Response): Promise<any> {
157149
const contentType = response.headers.get("content-type");
158150
if (/application\/json/.test(contentType!)) {
159151
return (

‎test/mock-request-http-server.ts

Copy file name to clipboardExpand all lines: test/mock-request-http-server.ts
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import defaults from "../src/defaults.ts";
1010

1111
export default async function mockRequestHttpServer(
1212
requestListener: RequestListener,
13+
fetch = globalThis.fetch,
1314
): Promise<
1415
RequestInterface<object> & {
1516
closeMockServer: () => void;
@@ -25,6 +26,9 @@ export default async function mockRequestHttpServer(
2526
const request = withDefaults(endpoint, {
2627
...defaults,
2728
baseUrl,
29+
request: {
30+
fetch,
31+
},
2832
}) as RequestInterface<object> & {
2933
closeMockServer: () => void;
3034
baseUrlMockServer: string;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.