mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
refactor: use abortable effect
This commit is contained in:
parent
05b89e111a
commit
9c12d344de
3 changed files with 114 additions and 98 deletions
|
|
@ -2,7 +2,7 @@ import { useConfigs } from 'containers/ConfigsContext'
|
|||
import { platform } from 'platforms'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useAbortableEffect } from 'utils/hooks/useAbortableEffect'
|
||||
import { useCatchNetworkError } from 'utils/hooks/useCatchNetworkError'
|
||||
import { useHandleNetworkError } from 'utils/hooks/useHandleNetworkError'
|
||||
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
|
||||
import { VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
|
||||
import { SideBarStateContext } from '../../../containers/SideBarState'
|
||||
|
|
@ -12,55 +12,58 @@ export function useVisibleNodesGenerator(metaData: MetaData | null) {
|
|||
null,
|
||||
)
|
||||
|
||||
const catchNetworkErrors = useCatchNetworkError()
|
||||
const config = useConfigs().value
|
||||
const setStateContext = useLoadedContext(SideBarStateContext).onChange
|
||||
const handleNetworkError = useHandleNetworkError()
|
||||
|
||||
// Only run when metadata or accessToken changes
|
||||
const createVNG = useCallback(
|
||||
async function* createVNG() {
|
||||
if (!metaData) return
|
||||
|
||||
setStateContext('tree-loading')
|
||||
const { userName, repoName, branchName } = metaData
|
||||
try {
|
||||
const { root: treeRoot, defer = false } = yield await platform.getTreeData(
|
||||
{
|
||||
branchName,
|
||||
userName,
|
||||
repoName,
|
||||
},
|
||||
'/',
|
||||
true,
|
||||
config.accessToken,
|
||||
)
|
||||
setStateContext('tree-rendering')
|
||||
|
||||
setVisibleNodesGenerator(
|
||||
new VisibleNodesGenerator({
|
||||
root: treeRoot,
|
||||
defer,
|
||||
compress: config.compressSingletonFolder,
|
||||
async getTreeData(path) {
|
||||
const { root } = await platform.getTreeData(metaData, path, false, config.accessToken)
|
||||
return root
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
setStateContext('tree-rendered')
|
||||
} catch (err) {
|
||||
if (err instanceof Error) handleNetworkError(err)
|
||||
else throw err
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[metaData, config.accessToken],
|
||||
)
|
||||
|
||||
useAbortableEffect(
|
||||
useCallback(
|
||||
signal => {
|
||||
catchNetworkErrors(async () => {
|
||||
if (!metaData) return
|
||||
if (signal.aborted) return
|
||||
|
||||
setStateContext('tree-loading')
|
||||
const { userName, repoName, branchName } = metaData
|
||||
const { root: treeRoot, defer = false } = await platform.getTreeData(
|
||||
{
|
||||
branchName,
|
||||
userName,
|
||||
repoName,
|
||||
},
|
||||
'/',
|
||||
true,
|
||||
config.accessToken,
|
||||
)
|
||||
if (signal.aborted) return
|
||||
|
||||
setStateContext('tree-rendering')
|
||||
|
||||
setVisibleNodesGenerator(
|
||||
new VisibleNodesGenerator({
|
||||
root: treeRoot,
|
||||
defer,
|
||||
compress: config.compressSingletonFolder,
|
||||
async getTreeData(path) {
|
||||
const { root } = await platform.getTreeData(
|
||||
metaData,
|
||||
path,
|
||||
false,
|
||||
config.accessToken,
|
||||
)
|
||||
return root
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
setStateContext('tree-rendered')
|
||||
})
|
||||
},
|
||||
[metaData, config.accessToken], // eslint-disable-line react-hooks/exhaustive-deps
|
||||
() => ({
|
||||
getAsyncGenerator: createVNG,
|
||||
}),
|
||||
[createVNG],
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { PropsWithChildren } from 'common'
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { platform } from 'platforms'
|
||||
import * as React from 'react'
|
||||
import { run } from 'utils/general'
|
||||
import { useAbortableEffect } from 'utils/hooks/useAbortableEffect'
|
||||
import { useEffectOnSerializableUpdates } from 'utils/hooks/useEffectOnSerializableUpdates'
|
||||
import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
|
||||
|
|
@ -36,10 +35,11 @@ export function RepoContextWrapper({ children }: PropsWithChildren) {
|
|||
function resolvePartialMetaData(): PartialMetaData | null {
|
||||
const partialMetaData = platform.resolvePartialMetaData()
|
||||
if (partialMetaData) {
|
||||
const { userName, repoName, type } = partialMetaData
|
||||
const { userName, repoName, branchName, type } = partialMetaData
|
||||
return {
|
||||
userName,
|
||||
repoName,
|
||||
branchName,
|
||||
type: type === 'pull' ? type : undefined,
|
||||
}
|
||||
}
|
||||
|
|
@ -76,59 +76,56 @@ function usePartialMetaData(): PartialMetaData | null {
|
|||
}
|
||||
|
||||
function useMetaData(partialMetaData: PartialMetaData | null) {
|
||||
const $state = useLoadedContext(SideBarStateContext)
|
||||
const $metaData = useStateIO<MetaData | null>(null)
|
||||
const [metaData, changeMetaData] = React.useState<MetaData | null>(null)
|
||||
const changeLoadedState = useLoadedContext(SideBarStateContext).onChange
|
||||
const handleNetworkError = useHandleNetworkError()
|
||||
|
||||
const { accessToken } = useConfigs().value
|
||||
const handleNetworkError = useHandleNetworkError()
|
||||
const loadRepoMetaData = React.useCallback(
|
||||
async function* loadRepoMetaData() {
|
||||
if (!partialMetaData) return
|
||||
|
||||
const { userName, repoName } = partialMetaData
|
||||
if (!userName || !repoName) return
|
||||
|
||||
changeLoadedState('meta-loading')
|
||||
let { branchName } = partialMetaData
|
||||
if (!branchName) {
|
||||
try {
|
||||
const defaultBranchName = yield await platform.getDefaultBranchName(
|
||||
{ userName, repoName },
|
||||
accessToken,
|
||||
)
|
||||
branchName = defaultBranchName as string
|
||||
} catch (err) {
|
||||
// state will be updated in the network error handler
|
||||
if (err instanceof Error) return handleNetworkError(err)
|
||||
else throw err
|
||||
}
|
||||
}
|
||||
|
||||
changeMetaData({
|
||||
userName,
|
||||
repoName,
|
||||
branchName,
|
||||
})
|
||||
changeLoadedState('meta-loaded')
|
||||
},
|
||||
[partialMetaData, changeLoadedState, accessToken, handleNetworkError],
|
||||
)
|
||||
|
||||
useAbortableEffect(
|
||||
React.useCallback(
|
||||
signal => {
|
||||
// get default branch
|
||||
run(async () => {
|
||||
if (!partialMetaData) return
|
||||
|
||||
const { userName, repoName } = partialMetaData
|
||||
if (!userName || !repoName) return
|
||||
|
||||
$state.onChange('meta-loading')
|
||||
let { branchName } = partialMetaData
|
||||
if (!branchName) {
|
||||
try {
|
||||
const defaultBranchName = await platform.getDefaultBranchName(
|
||||
{ userName, repoName },
|
||||
accessToken,
|
||||
)
|
||||
if (signal.aborted) return
|
||||
branchName = defaultBranchName
|
||||
} catch (err) {
|
||||
// state will be updated in the network error handler
|
||||
if (err instanceof Error) {
|
||||
handleNetworkError(err)
|
||||
return
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
$metaData.onChange({
|
||||
userName,
|
||||
repoName,
|
||||
branchName,
|
||||
})
|
||||
$state.onChange('meta-loaded')
|
||||
})
|
||||
|
||||
return () => {
|
||||
$state.onChange('disabled')
|
||||
$metaData.onChange(null)
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[partialMetaData, accessToken],
|
||||
() => ({
|
||||
getAsyncGenerator: loadRepoMetaData,
|
||||
cancel: () => {
|
||||
changeLoadedState('disabled')
|
||||
changeMetaData(null)
|
||||
},
|
||||
}),
|
||||
[loadRepoMetaData, changeLoadedState, changeMetaData],
|
||||
),
|
||||
)
|
||||
|
||||
return $metaData.value
|
||||
return metaData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,32 @@ import { useEffect } from 'react'
|
|||
* This effect addresses such a problem:
|
||||
* the later effect ends earlier than the previous one, and the previous effect overlaps later effect's result.
|
||||
*/
|
||||
export function useAbortableEffect(
|
||||
effect: (shouldAbort: AbortSignal) => (() => void | undefined) | void,
|
||||
export function useAbortableEffect<T, TReturn, TNext>(
|
||||
effect: () => {
|
||||
getAsyncGenerator: () => AsyncGenerator<T, TReturn, TNext>
|
||||
cancel?: () => void
|
||||
},
|
||||
) {
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController()
|
||||
// The previous effect should stop running if the signal indicates should abort
|
||||
const defect = effect(abortController.signal)
|
||||
const { getAsyncGenerator, cancel } = effect()
|
||||
runAbortableAsyncGenerator(getAsyncGenerator(), abortController.signal)
|
||||
return () => {
|
||||
cancel?.()
|
||||
abortController.abort()
|
||||
defect?.()
|
||||
}
|
||||
}, [effect])
|
||||
}
|
||||
|
||||
async function runAbortableAsyncGenerator<T>(
|
||||
generator: AsyncGenerator<unknown, T, unknown>,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
let latestResult: IteratorResult<unknown> | undefined
|
||||
do {
|
||||
if (signal?.aborted) return
|
||||
latestResult = await generator.next(await latestResult?.value)
|
||||
} while (!latestResult.done)
|
||||
return latestResult.value
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue