mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-17 09:18:26 +00:00
fix: make useApiRequest loading state concurrency-safe
Replace the single boolean isLoading ref with a pending-request counter exposed as a computed, so concurrent executeRequest calls on the same composable instance no longer flip isLoading back to false while siblings are still in flight. Update service mocks to the new ComputedRef type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,29 @@ describe('useApiRequest', () => {
|
||||
expect(error.value).toBeNull()
|
||||
})
|
||||
|
||||
it('stays loading until every concurrent request settles', async () => {
|
||||
const { isLoading, executeRequest } = useApiRequest({
|
||||
client,
|
||||
mapError: vi.fn()
|
||||
})
|
||||
|
||||
let resolveFirst!: (value: AxiosResponse<string>) => void
|
||||
const first = executeRequest(
|
||||
() => new Promise<AxiosResponse<string>>((res) => (resolveFirst = res)),
|
||||
{ errorContext: 'ctx' }
|
||||
)
|
||||
const second = executeRequest(async () => response('second'), {
|
||||
errorContext: 'ctx'
|
||||
})
|
||||
|
||||
await second
|
||||
expect(isLoading.value).toBe(true)
|
||||
|
||||
resolveFirst(response('first'))
|
||||
await first
|
||||
expect(isLoading.value).toBe(false)
|
||||
})
|
||||
|
||||
it('passes the injected client to the api call', async () => {
|
||||
const apiCall = vi.fn(async () => response(1))
|
||||
const { executeRequest } = useApiRequest({ client, mapError: vi.fn() })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { isAbortError } from '@/utils/typeGuardUtil'
|
||||
|
||||
@@ -33,7 +33,8 @@ export function useApiRequest({
|
||||
client: AxiosInstance
|
||||
mapError: ApiErrorMapper
|
||||
}) {
|
||||
const isLoading = ref(false)
|
||||
const pendingCount = ref(0)
|
||||
const isLoading = computed(() => pendingCount.value > 0)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function executeRequest<T>(
|
||||
@@ -42,7 +43,7 @@ export function useApiRequest({
|
||||
): Promise<T | null> {
|
||||
const { errorContext, routeSpecificErrors, onSuccess } = options
|
||||
|
||||
isLoading.value = true
|
||||
pendingCount.value++
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
@@ -55,7 +56,7 @@ export function useApiRequest({
|
||||
error.value = mapError(err, errorContext, routeSpecificErrors)
|
||||
return null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
pendingCount.value--
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||
@@ -69,7 +70,7 @@ const mockListResult: operations['listAllNodes']['responses'][200]['content']['a
|
||||
|
||||
describe('useComfyRegistryStore', () => {
|
||||
let mockRegistryService: {
|
||||
isLoading: ReturnType<typeof ref<boolean>>
|
||||
isLoading: ComputedRef<boolean>
|
||||
error: ReturnType<typeof ref<string | null>>
|
||||
listAllPacks: ReturnType<typeof vi.fn>
|
||||
getPackById: ReturnType<typeof vi.fn>
|
||||
@@ -87,7 +88,7 @@ describe('useComfyRegistryStore', () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
mockRegistryService = {
|
||||
isLoading: ref(false),
|
||||
isLoading: computed(() => false),
|
||||
error: ref(null),
|
||||
listAllPacks: vi.fn().mockImplementation((params) => {
|
||||
// If node_id is provided, return specific nodes
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('useConflictDetection', () => {
|
||||
|
||||
const mockComfyManagerService = {
|
||||
getImportFailInfoBulk: vi.fn(),
|
||||
isLoading: ref(false),
|
||||
isLoading: computed(() => false),
|
||||
error: ref<string | null>(null)
|
||||
} as Partial<ReturnType<typeof useComfyManagerService>> as ReturnType<
|
||||
typeof useComfyManagerService
|
||||
@@ -128,7 +128,7 @@ describe('useConflictDetection', () => {
|
||||
|
||||
const mockRegistryService = {
|
||||
getBulkNodeVersions: vi.fn(),
|
||||
isLoading: ref(false),
|
||||
isLoading: computed(() => false),
|
||||
error: ref<string | null>(null)
|
||||
} as Partial<ReturnType<typeof useComfyRegistryService>> as ReturnType<
|
||||
typeof useComfyRegistryService
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
@@ -80,7 +80,7 @@ describe('useComfyManagerStore', () => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
mockManagerService = {
|
||||
isLoading: ref(false),
|
||||
isLoading: computed(() => false),
|
||||
error: ref(null),
|
||||
startQueue: vi.fn().mockResolvedValue(null),
|
||||
getQueueStatus: vi.fn().mockResolvedValue(null),
|
||||
|
||||
Reference in New Issue
Block a user