fix: Use retryOnThrottled

This commit is contained in:
lunya.pet 2025-06-29 12:42:07 +02:00
parent 7fcce1c7ef
commit 04605283f9
3 changed files with 37 additions and 62 deletions

View file

@ -90,6 +90,7 @@ import MkPagingButtons from '@/components/MkPagingButtons.vue';
import { selectFile } from '@/utility/select-file.js'; import { selectFile } from '@/utility/select-file.js';
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js'; import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
import { useLoading } from '@/components/hook/useLoading.js'; import { useLoading } from '@/components/hook/useLoading.js';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
type GridItem = { type GridItem = {
checked: boolean; checked: boolean;
@ -334,37 +335,29 @@ async function onUpdateButtonClicked() {
err?: unknown; err?: unknown;
}; };
const executeWithRetries = async (item: any, retries: number = 3): Promise<ApiResponse> => { const execute = async (item: any): Promise<ApiResponse> => {
for (let attempt = 0; attempt <= retries; attempt++) { try {
try { await retryOnThrottled(() => misskeyApi('admin/emoji/update', {
await misskeyApi('admin/emoji/update', { id: item.id,
id: item.id, name: item.name,
name: item.name, category: emptyStrToNull(item.category),
category: emptyStrToNull(item.category), aliases: emptyStrToEmptyArray(item.aliases),
aliases: emptyStrToEmptyArray(item.aliases), license: emptyStrToNull(item.license),
license: emptyStrToNull(item.license), isSensitive: item.isSensitive,
isSensitive: item.isSensitive, localOnly: item.localOnly,
localOnly: item.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map((it: any) => it.id),
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map((it: any) => it.id), fileId: item.fileId,
fileId: item.fileId, }));
}); return { item, success: true };
return { item, success: true }; } catch (error) {
} catch (err) { return { item, success: false, err: error };
if (attempt < retries) {
console.warn(`Retrying ${item.name}, attempt ${attempt + 1}`);
await delay(1000 * (attempt + 1));
} else {
return { item, success: false, err };
}
}
} }
return { item, success: false, err: new Error('Unknown error') };
}; };
const action = async (): Promise<ApiResponse[]> => { const action = async (): Promise<ApiResponse[]> => {
const results: ApiResponse[] = []; const results: ApiResponse[] = [];
for (const item of updatedItems) { for (const item of updatedItems) {
results.push(await executeWithRetries(item)); results.push(await execute(item));
} }
return results; return results;
}; };

View file

@ -75,6 +75,7 @@ SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { onMounted, ref, useCssModule } from 'vue'; import { onMounted, ref, useCssModule } from 'vue';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js'; import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js'; import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import type { DroppedFile } from '@/utility/file-drop.js'; import type { DroppedFile } from '@/utility/file-drop.js';
@ -247,29 +248,19 @@ const registerButtonDisabled = ref<boolean>(false);
const requestLogs = ref<RequestLogItem[]>([]); const requestLogs = ref<RequestLogItem[]>([]);
const isDragOver = ref<boolean>(false); const isDragOver = ref<boolean>(false);
const delay = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms));
type ApiResponse = { type ApiResponse = {
item: any; item: any;
success: boolean; success: boolean;
err?: unknown; err?: unknown;
}; };
const executeWithRetries = async (item: any, apiEndpoint: string, payload: any, retries = 3): Promise<ApiResponse> => { const execute = async (item: any, apiEndpoint: string, payload: any): Promise<ApiResponse> => {
for (let attempt = 0; attempt <= retries; attempt++) { try {
try { await retryOnThrottled(() => misskeyApi(apiEndpoint, payload));
await misskeyApi(apiEndpoint, payload); return { item, success: true };
return { item, success: true }; } catch (err) {
} catch (err) { return { item, success: false, err };
if (attempt < retries) {
console.warn(`Retrying ${item.id || item.name}, attempt ${attempt + 1}`);
await delay(1000 * (attempt + 1)); // Exponential backoff
} else {
return { item, success: false, err };
}
}
} }
return { item, success: false, err: new Error('Unknown error') }; // Ensures all code paths return a value
}; };
const importEmojis = async (targets: any[]): Promise<void> => { const importEmojis = async (targets: any[]): Promise<void> => {
@ -286,7 +277,7 @@ const importEmojis = async (targets: any[]): Promise<void> => {
async function action(): Promise<ApiResponse[]> { async function action(): Promise<ApiResponse[]> {
const results: ApiResponse[] = []; const results: ApiResponse[] = [];
for (const item of targets) { for (const item of targets) {
results.push(await executeWithRetries(item, 'admin/emoji/copy', { emojiId: item.id })); results.push(await execute(item, 'admin/emoji/copy', { emojiId: item.id }));
} }
return results; return results;
@ -326,7 +317,7 @@ const onRegistryClicked = async (): Promise<void> => {
const results: ApiResponse[] = []; const results: ApiResponse[] = [];
for (const item of items) { for (const item of items) {
results.push( results.push(
await executeWithRetries(item, 'admin/emoji/add', { await execute(item, 'admin/emoji/add', {
name: item.name, name: item.name,
category: emptyStrToNull(item.category), category: emptyStrToNull(item.category),
aliases: emptyStrToEmptyArray(item.aliases), aliases: emptyStrToEmptyArray(item.aliases),

View file

@ -142,6 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, useCssModule } from 'vue'; import { computed, onMounted, ref, useCssModule } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
import type { GridSortOrderKey, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js'; import type { GridSortOrderKey, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
import type { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js'; import type { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import type { GridSetting } from '@/components/grid/grid.js'; import type { GridSetting } from '@/components/grid/grid.js';
@ -310,31 +311,21 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
} }
} }
const delay = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms));
type ApiResponse = { type ApiResponse = {
item: any; item: any;
success: boolean; success: boolean;
err?: unknown; err?: unknown;
}; };
const executeWithRetries = async (item: any, retries: number = 3): Promise<ApiResponse> => { const execute = async (item: any): Promise<ApiResponse> => {
for (let attempt = 0; attempt <= retries; attempt++) { try {
try { await retryOnThrottled(() => misskeyApi('admin/emoji/copy', {
await misskeyApi('admin/emoji/copy', { emojiId: item.id,
emojiId: item.id, }));
}); return { item, success: true };
return { item, success: true }; } catch (err) {
} catch (err) { return { item, success: false, err };
if (attempt < retries) {
console.warn(`Retrying ${item.id}, attempt ${attempt + 1}`);
await delay(1000 * (attempt + 1)); // Exponential backoff
} else {
return { item, success: false, err };
}
}
} }
return { item, success: false, err: new Error('Unknown error') }; // Ensures all code paths return a value
}; };
const importEmojis = async (targets: any[]): Promise<void> => { const importEmojis = async (targets: any[]): Promise<void> => {
@ -350,7 +341,7 @@ const importEmojis = async (targets: any[]): Promise<void> => {
const results: ApiResponse[] = []; const results: ApiResponse[] = [];
for (const item of targets) { for (const item of targets) {
results.push(await executeWithRetries(item)); results.push(await execute(item));
} }
const failedItems = results.filter(it => !it.success); const failedItems = results.filter(it => !it.success);