Skip to content

Commit b52c4d5

Browse files
committed
fixes #637 - Allow aborting fetch request
1 parent f53e740 commit b52c4d5

File tree

5 files changed

+291
-17
lines changed

5 files changed

+291
-17
lines changed

docs/api.html

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ <h2> Events</h2>
685685
<div class="table-block">
686686
<table class="m-table">
687687
<tr>
688-
<th class="right" style="width:130px">Event</th>
688+
<th class="right" style="width:150px">Event</th>
689689
<th class="left">Description </th>
690690
</tr>
691691
<tr>
@@ -706,6 +706,31 @@ <h2> Events</h2>
706706
fired before user clicks try.
707707
Provides a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request" target="_blank">request object</a>
708708
that can be modified before making the final call to the API.
709+
710+
<br/>
711+
Example: To add a custom header before making a try call
712+
<pre class="shadow1 code-block" style = "width:500px">
713+
<code class="language-js">
714+
rapidocEl.addEventListener('before-try', (e) => {
715+
if (e.detail.request.method === 'POST') {
716+
e.detail.request.headers.append('my-header', 'XYX');
717+
}
718+
});
719+
</code>
720+
</pre>
721+
722+
<br/>
723+
Example: To abort a (Try)request - Below example aborts all DELETE calls
724+
<pre class="shadow1 code-block" style = "width:500px">
725+
<code class="language-js">
726+
rapidocEl.addEventListener('before-try', (e) => {
727+
if (e.detail.request.method === 'DELETE') {
728+
e.detail.controller.abort();
729+
}
730+
});
731+
</code>
732+
</pre>
733+
709734
</td>
710735
</tr>
711736
<tr>
@@ -715,13 +740,22 @@ <h2> Events</h2>
715740
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Request" target="_blank">request</a> and
716741
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response" target="_blank">response</a> object
717742
In case of an error it will provide an <span> request</span> and <span> err</span> object
718-
<a href="https://github.com/mrin9/RapiDoc/blob/master/docs/examples/mock.html"> Example </a>
743+
744+
</td>
745+
</tr>
746+
<tr>
747+
<td class="mono bold right">request-aborted</td>
748+
<td class="gray">
749+
fired when user aborts the request
719750
</td>
720751
</tr>
721752
<tr>
722753
<td class="mono bold right">api-server-change</td>
723754
<td class="gray">fires when you change the API server </td>
724755
</tr>
756+
<tr><td colspan="2" style = "text-align: center;">
757+
Working <a href="./examples/events.html"> Example </a> of Event Handling In RapiDoc
758+
</td></tr>
725759
</table>
726760
</div>
727761

docs/examples/events.html

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<!-- Global site tag (gtag.js) - Google Analytics -->
5+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132775238-1"></script>
6+
<script>
7+
window.dataLayer = window.dataLayer || [];
8+
function gtag(){dataLayer.push(arguments);}
9+
gtag('js', new Date());
10+
gtag('config', 'UA-132775238-1');
11+
</script>
12+
13+
<meta charset="utf-8">
14+
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
15+
<script type="module" src="../rapidoc-min.js"></script>
16+
</head>
17+
<body>
18+
<rapi-doc id="thedoc"
19+
spec-url = "../specs/events.yaml"
20+
allow-authentication = "false"
21+
allow-server-selection = "false"
22+
show-header="false"
23+
render-style = "focus"
24+
>
25+
</rapi-doc>
26+
27+
<script>
28+
document.addEventListener('DOMContentLoaded', (event) => {
29+
let docEl = document.getElementById("thedoc");
30+
31+
docEl.addEventListener('before-render', (e) => {
32+
e.detail.spec.info.title = "EVENTS - This text is updated during `before-render` event";
33+
});
34+
35+
docEl.addEventListener('after-try', (e) => {
36+
alert("Hello from 'after-try' event ");
37+
});
38+
39+
docEl.addEventListener('before-try', (e) => {
40+
if (e.detail.request.method === 'POST') {
41+
setTimeout(() => {
42+
e.detail.controller.abort();
43+
}, 0);
44+
}
45+
});
46+
47+
docEl.addEventListener('request-aborted', (e) => {
48+
alert("POST Requests are aborted in 'before-try' event ");
49+
});
50+
})
51+
</script>
52+
53+
</body>
54+
</html>

docs/examples/mock.html

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
render-style = "read"
2323
sort-tags = "true"
2424
primary-color="#f54c47"
25-
> </rapi-doc>
25+
>
26+
27+
</rapi-doc>
2628

2729
<script>
2830
document.addEventListener('DOMContentLoaded', (event) => {
@@ -33,8 +35,20 @@
3335
});
3436

3537
docEl.addEventListener('after-try', (e) => {
36-
console.log(e.detail);
38+
console.log('Try Complete:', e.detail);
39+
});
40+
41+
docEl.addEventListener('before-try', (e) => {
42+
// e.detail.controller.abort('Reason to abort');
43+
setTimeout(() => {
44+
e.detail.controller.abort('Reason to abort');
45+
}, 100);
3746
});
47+
48+
docEl.addEventListener('Request Aborted:', (e) => {
49+
console.log('Request Aborted', e);
50+
});
51+
3852
})
3953
</script>
4054

docs/specs/events.yaml

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Event Handling
4+
version: "1.0"
5+
description: |
6+
7+
### The code demonstrate how some events can be used. Listed below are some common use cases
8+
9+
- `before-render` - Good place to twaek the OpenAPI spec if needed. In this example the Title above is dynamically created
10+
- `before-try` - Good place to twaek the AJAX request sucha as adding a request header or abort the request
11+
- `after-try` - Good place to inspect the response received
12+
13+
#### To test it out go ahead and click TRY below, you will notice all `POST` requests are aborted and `GET` are success <br/><br/>
14+
15+
16+
```markup
17+
<!doctype html>
18+
19+
<html>
20+
<body>
21+
<rapi-doc id = "thedoc" spec-url = "..."> </rapi-doc>
22+
23+
<script>
24+
document.addEventListener('DOMContentLoaded', (event) => {
25+
let docEl = document.getElementById("thedoc");
26+
27+
// Add various event listeners
28+
29+
// 1. before-render (Dynamically changes the Title of this Spec)
30+
docEl.addEventListener('before-render', (e) => {
31+
e.detail.spec.info.title = "EVENTS - This text is updated during `before-render` event";
32+
});
33+
34+
// 2. before-try (Aborts all post calls)
35+
docEl.addEventListener('before-try', (e) => {
36+
if (e.detail.request.method === 'POST') {
37+
e.detail.controller.abort();
38+
}
39+
});
40+
41+
// 3. after-try
42+
docEl.addEventListener('after-try', (e) => {
43+
alert("Hello from 'after-try' event ");
44+
});
45+
46+
// 4. request-aborted
47+
docEl.addEventListener('request-aborted', (e) => {
48+
calert("POST Requests are aborted in 'before-try' event ");
49+
});
50+
51+
})
52+
</script>
53+
</body>
54+
55+
</html>
56+
```
57+
servers:
58+
- url: https://reqres.in/api/
59+
paths:
60+
/users:
61+
get:
62+
description: List of users (paginated)
63+
parameters:
64+
- name: page
65+
in: query
66+
schema:
67+
type: integer
68+
examples:
69+
- 1
70+
- 2
71+
responses:
72+
'200':
73+
description: Successful operation
74+
content:
75+
application/json:
76+
schema:
77+
type: object
78+
description: Description of **User** object
79+
properties:
80+
page:
81+
description: Current Page number
82+
type: integer
83+
per_page:
84+
description: Number of records per page
85+
type: integer
86+
total:
87+
description: Total number of records
88+
type: integer
89+
total_pages:
90+
type: integer
91+
data:
92+
type: array
93+
description: List of users
94+
items:
95+
$ref: '#/components/schemas/user'
96+
support:
97+
$ref: '#/components/schemas/support'
98+
post:
99+
description: Create a user
100+
requestBody:
101+
content:
102+
application/json:
103+
schema:
104+
$ref: "#/components/schemas/userInput"
105+
responses:
106+
201:
107+
description: User creation response
108+
content:
109+
application/json:
110+
schema:
111+
allOf:
112+
- $ref: '#/components/schemas/userInput'
113+
- $ref: '#/components/schemas/createUserResponse'
114+
115+
components:
116+
schemas:
117+
user:
118+
type: object
119+
properties:
120+
id:
121+
description: User ID
122+
type: integer
123+
email:
124+
description: User Email
125+
type: string
126+
first_name:
127+
description: First Name
128+
type: string
129+
last_name:
130+
description: Last Name
131+
type: string
132+
avatar:
133+
description: Avatar URL
134+
type: string
135+
support:
136+
type: object
137+
properties:
138+
url:
139+
description: Support URL
140+
type: string
141+
text:
142+
description: Support URL - Description
143+
type: string
144+
userInput:
145+
type: object
146+
description: user object with `name` and `job` properties
147+
properties:
148+
name:
149+
description: User Name
150+
type: string
151+
job:
152+
description: Job
153+
type: string
154+
createUserResponse:
155+
type: object
156+
properties:
157+
id:
158+
type: integer
159+
createdAt:
160+
type: string

src/components/api-request.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,6 @@ export default class ApiRequest extends LitElement {
10571057
const queryParamObjTypeEls = [...requestPanelEl.querySelectorAll("[data-ptype='query-object']")];
10581058
const headerParamEls = [...requestPanelEl.querySelectorAll("[data-ptype='header']")];
10591059
const requestBodyContainerEl = requestPanelEl.querySelector('.request-body-container');
1060-
10611060
fetchUrl = this.path;
10621061
const fetchOptions = {
10631062
method: this.method.toUpperCase(),
@@ -1321,13 +1320,16 @@ export default class ApiRequest extends LitElement {
13211320
if (this.fetchCredentials) {
13221321
fetchOptions.credentials = this.fetchCredentials;
13231322
}
1323+
const controller = new AbortController();
1324+
const { signal } = controller;
13241325
fetchOptions.headers = reqHeaders;
13251326
const fetchRequest = new Request(fetchUrl, fetchOptions);
13261327
this.dispatchEvent(new CustomEvent('before-try', {
13271328
bubbles: true,
13281329
composed: true,
13291330
detail: {
13301331
request: fetchRequest,
1332+
controller,
13311333
},
13321334
}));
13331335

@@ -1339,7 +1341,7 @@ export default class ApiRequest extends LitElement {
13391341
let respText;
13401342
tryBtnEl.disabled = true;
13411343
const startTime = performance.now();
1342-
fetchResponse = await fetch(fetchRequest);
1344+
fetchResponse = await fetch(fetchRequest, { signal });
13431345
const endTime = performance.now();
13441346
responseClone = fetchResponse.clone(); // create a response clone to allow reading response body again (response.json, response.text etc)
13451347
tryBtnEl.disabled = false;
@@ -1411,17 +1413,27 @@ export default class ApiRequest extends LitElement {
14111413
}));
14121414
} catch (err) {
14131415
tryBtnEl.disabled = false;
1414-
this.responseMessage = `${err.message} (CORS or Network Issue)`;
1415-
document.dispatchEvent(new CustomEvent('after-try', {
1416-
bubbles: true,
1417-
composed: true,
1418-
detail: {
1419-
err,
1420-
request: fetchRequest,
1421-
response: responseClone,
1422-
responseStatus: responseClone.ok,
1423-
},
1424-
}));
1416+
if (err.name === 'AbortError') {
1417+
this.dispatchEvent(new CustomEvent('request-aborted', {
1418+
bubbles: true,
1419+
composed: true,
1420+
detail: {
1421+
err,
1422+
request: fetchRequest,
1423+
},
1424+
}));
1425+
this.responseMessage = 'Request Aborted';
1426+
} else {
1427+
this.dispatchEvent(new CustomEvent('after-try', {
1428+
bubbles: true,
1429+
composed: true,
1430+
detail: {
1431+
err,
1432+
request: fetchRequest,
1433+
},
1434+
}));
1435+
this.responseMessage = `${err.message} (CORS or Network Issue)`;
1436+
}
14251437
}
14261438
this.requestUpdate();
14271439
}

0 commit comments

Comments
 (0)