Skip to content

Commit 101f5d2

Browse files
wxiaoguangStelios Malathouras
authored and
Stelios Malathouras
committed
Fix some incorrect async functions, improve frontend document. (go-gitea#17597)
1 parent c8039e9 commit 101f5d2

File tree

7 files changed

+107
-44
lines changed

7 files changed

+107
-44
lines changed

docs/content/doc/developers/guidelines-frontend.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,65 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
3939
6. The backend can pass complex data to the frontend by using `ctx.PageData["myModuleData"] = map[]{}`
4040
7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
4141

42+
43+
### `async` Functions
44+
45+
Only mark a function as `async` if and only if there are `await` calls
46+
or `Promise` returns inside the function.
47+
48+
It's not recommended to use `async` event listeners, which may lead to problems.
49+
The reason is that the code after await is executed outside the event dispatch.
50+
Reference: https://github.com/github/eslint-plugin-github/blob/main/docs/rules/async-preventdefault.md
51+
52+
If we want to call an `async` function in a non-async context,
53+
it's recommended to use `const _promise = asyncFoo()` to tell readers
54+
that this is done by purpose, we want to call the async function and ignore the Promise.
55+
Some lint rules and IDEs also have warnings if the returned Promise is not handled.
56+
57+
#### DOM Event Listener
58+
59+
```js
60+
el.addEventListener('click', (e) => {
61+
(async () => {
62+
await asyncFoo(); // recommended
63+
// then we shound't do e.preventDefault() after await, no effect
64+
})();
65+
66+
const _promise = asyncFoo(); // recommended
67+
68+
e.preventDefault(); // correct
69+
});
70+
71+
el.addEventListener('async', async (e) => { // not recommended but acceptable
72+
e.preventDefault(); // acceptable
73+
await asyncFoo(); // skip out event dispath
74+
e.preventDefault(); // WRONG
75+
});
76+
```
77+
78+
#### jQuery Event Listener
79+
80+
```js
81+
$('#el').on('click', (e) => {
82+
(async () => {
83+
await asyncFoo(); // recommended
84+
// then we shound't do e.preventDefault() after await, no effect
85+
})();
86+
87+
const _promise = asyncFoo(); // recommended
88+
89+
e.preventDefault(); // correct
90+
return false; // correct
91+
});
92+
93+
$('#el').on('click', async (e) => { // not recommended but acceptable
94+
e.preventDefault(); // acceptable
95+
return false; // WRONG, jQuery expects the returned value is a boolean, not a Promise
96+
await asyncFoo(); // skip out event dispath
97+
return false; // WRONG
98+
});
99+
```
100+
42101
### Vue2/Vue3 and JSX
43102

44103
Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.

web_src/js/features/clipboard.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function fallbackCopyToClipboard(text) {
4646
}
4747

4848
export default function initGlobalCopyToClipboardListener() {
49-
document.addEventListener('click', async (e) => {
49+
document.addEventListener('click', (e) => {
5050
let target = e.target;
5151
// in case <button data-clipboard-text><svg></button>, so we just search up to 3 levels for performance.
5252
for (let i = 0; i < 3 && target; i++) {
@@ -58,16 +58,20 @@ export default function initGlobalCopyToClipboardListener() {
5858
}
5959
if (text) {
6060
e.preventDefault();
61-
try {
62-
await navigator.clipboard.writeText(text);
63-
onSuccess(target);
64-
} catch {
65-
if (fallbackCopyToClipboard(text)) {
61+
62+
(async() => {
63+
try {
64+
await navigator.clipboard.writeText(text);
6665
onSuccess(target);
67-
} else {
68-
onError(target);
66+
} catch {
67+
if (fallbackCopyToClipboard(text)) {
68+
onSuccess(target);
69+
} else {
70+
onError(target);
71+
}
6972
}
70-
}
73+
})();
74+
7175
break;
7276
}
7377
target = target.parentElement;

web_src/js/features/notification.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ const {appSubUrl, csrfToken, notificationSettings} = window.config;
33
let notificationSequenceNumber = 0;
44

55
export function initNotificationsTable() {
6-
$('#notification_table .button').on('click', async function () {
7-
const data = await updateNotification(
8-
$(this).data('url'),
9-
$(this).data('status'),
10-
$(this).data('page'),
11-
$(this).data('q'),
12-
$(this).data('notification-id'),
13-
);
14-
15-
if ($(data).data('sequence-number') === notificationSequenceNumber) {
16-
$('#notification_div').replaceWith(data);
17-
initNotificationsTable();
18-
}
19-
await updateNotificationCount();
20-
6+
$('#notification_table .button').on('click', function () {
7+
(async () => {
8+
const data = await updateNotification(
9+
$(this).data('url'),
10+
$(this).data('status'),
11+
$(this).data('page'),
12+
$(this).data('q'),
13+
$(this).data('notification-id'),
14+
);
15+
16+
if ($(data).data('sequence-number') === notificationSequenceNumber) {
17+
$('#notification_div').replaceWith(data);
18+
initNotificationsTable();
19+
}
20+
await updateNotificationCount();
21+
})();
2122
return false;
2223
});
2324
}
@@ -104,8 +105,8 @@ export function initNotificationCount() {
104105
}
105106

106107
const fn = (timeout, lastCount) => {
107-
setTimeout(async () => {
108-
await updateNotificationCountWithCallback(fn, timeout, lastCount);
108+
setTimeout(() => {
109+
const _promise = updateNotificationCountWithCallback(fn, timeout, lastCount);
109110
}, timeout);
110111
};
111112

web_src/js/features/repo-graph.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function initRepoGraphGit() {
4848
});
4949
const url = new URL(window.location);
5050
const params = url.searchParams;
51-
const updateGraph = async () => {
51+
const updateGraph = () => {
5252
const queryString = params.toString();
5353
const ajaxUrl = new URL(url);
5454
ajaxUrl.searchParams.set('div-only', 'true');
@@ -57,14 +57,15 @@ export default function initRepoGraphGit() {
5757
$('#rel-container').addClass('hide');
5858
$('#rev-container').addClass('hide');
5959
$('#loading-indicator').removeClass('hide');
60-
61-
const div = $(await $.ajax(String(ajaxUrl)));
62-
$('#pagination').html(div.find('#pagination').html());
63-
$('#rel-container').html(div.find('#rel-container').html());
64-
$('#rev-container').html(div.find('#rev-container').html());
65-
$('#loading-indicator').addClass('hide');
66-
$('#rel-container').removeClass('hide');
67-
$('#rev-container').removeClass('hide');
60+
(async () => {
61+
const div = $(await $.ajax(String(ajaxUrl)));
62+
$('#pagination').html(div.find('#pagination').html());
63+
$('#rel-container').html(div.find('#rel-container').html());
64+
$('#rev-container').html(div.find('#rev-container').html());
65+
$('#loading-indicator').addClass('hide');
66+
$('#rel-container').removeClass('hide');
67+
$('#rev-container').removeClass('hide');
68+
})();
6869
};
6970
const dropdownSelected = params.getAll('branch');
7071
if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {

web_src/js/features/repo-legacy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ export function initRepository() {
351351

352352
// Edit issue or comment content
353353
$(document).on('click', '.edit-content', async function (event) {
354+
event.preventDefault();
354355
$(this).closest('.dropdown').find('.menu').toggle('visible');
355356
const $segment = $(this).closest('.header').next();
356357
const $editContentZone = $segment.find('.edit-content-zone');
@@ -511,7 +512,6 @@ export function initRepository() {
511512
$textarea.focus();
512513
$simplemde.codemirror.focus();
513514
});
514-
event.preventDefault();
515515
});
516516

517517
initRepoIssueCommentDelete();

web_src/js/features/repo-projects.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ export default function initRepoProject() {
6363
return;
6464
}
6565

66-
(async () => {
67-
await initRepoProjectSortable();
68-
})();
66+
const _promise = initRepoProjectSortable();
6967

7068
$('.edit-project-board').each(function () {
7169
const projectHeader = $(this).closest('.board-column-header');

web_src/js/features/stopwatch.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ export function initStopwatch() {
8282
}
8383

8484
const fn = (timeout) => {
85-
setTimeout(async () => {
86-
await updateStopwatchWithCallback(fn, timeout);
85+
setTimeout(() => {
86+
const _promise = updateStopwatchWithCallback(fn, timeout);
8787
}, timeout);
8888
};
8989

@@ -122,7 +122,7 @@ async function updateStopwatch() {
122122
return updateStopwatchData(data);
123123
}
124124

125-
async function updateStopwatchData(data) {
125+
function updateStopwatchData(data) {
126126
const watch = data[0];
127127
const btnEl = $('.active-stopwatch-trigger');
128128
if (!watch) {
@@ -135,14 +135,14 @@ async function updateStopwatchData(data) {
135135
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
136136
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
137137
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
138-
await updateStopwatchTime(seconds);
138+
updateStopwatchTime(seconds);
139139
btnEl.removeClass('hidden');
140140
}
141141

142142
return !!data.length;
143143
}
144144

145-
async function updateStopwatchTime(seconds) {
145+
function updateStopwatchTime(seconds) {
146146
const secs = parseInt(seconds);
147147
if (!Number.isFinite(secs)) return;
148148

0 commit comments

Comments
 (0)