Skip to content

Instantly share code, notes, and snippets.

@buYoung
Created August 6, 2024 05:07
Show Gist options
  • Save buYoung/890ecd4fcecd4d23e8f354b1b3988479 to your computer and use it in GitHub Desktop.
Save buYoung/890ecd4fcecd4d23e8f354b1b3988479 to your computer and use it in GitHub Desktop.
import type { DependencyList } from 'react';
import { useCallback, useEffect, useRef } from 'react';
type EffectCallback = () => void | Promise<void> | Generator<any, void, any> | (() => void);
// 타입 가드 함수들
function isGenerator(obj: any): obj is Generator {
return obj && typeof obj.next === 'function' && typeof obj.throw === 'function';
}
function isPromise<T>(obj: any): obj is Promise<any> {
return obj && typeof obj.then === 'function';
}
function isFunction(obj: any): obj is Function {
return typeof obj === 'function';
}
type Cleanup = () => void;
function useAsyncEffect(effect: EffectCallback | (() => void), deps?: DependencyList): void {
const isMountedRef = useRef(true);
const isExecutingRef = useRef(false);
const latestEffectRef = useRef<EffectCallback>(effect);
const cleanupRef = useRef<Cleanup | undefined>();
// 최신 effect 함수를 ref에 저장
latestEffectRef.current = effect;
const executeEffect = useCallback(async () => {
if (isExecutingRef.current) return;
isExecutingRef.current = true;
try {
if (cleanupRef.current) {
cleanupRef.current();
cleanupRef.current = undefined;
}
const result = latestEffectRef.current();
if (isGenerator(result)) {
// Generator 처리
const generator = result;
let res = generator.next();
while (!res.done) {
if (!isMountedRef.current) return;
res = generator.next(await res.value);
}
if (isMountedRef.current && typeof res.value === 'function') {
cleanupRef.current = res.value;
}
} else if (isPromise(result)) {
// Promise 처리
const cleanup = await result;
if (isMountedRef.current && typeof cleanup === 'function') {
cleanupRef.current = cleanup;
}
} else if (typeof result === 'function') {
// 동기 cleanup 함수 처리
cleanupRef.current = result;
}
} catch (error) {
console.error('Error in useAsyncEffect:', error);
} finally {
isExecutingRef.current = false;
}
}, []);
useEffect(() => {
executeEffect();
return () => {
isMountedRef.current = false;
if (cleanupRef.current) {
cleanupRef.current();
}
};
}, deps);
}
export default useAsyncEffect;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment