Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: useAsyncQueue #584

Open
1 of 2 tasks
haejunejung opened this issue Nov 16, 2024 · 16 comments
Open
1 of 2 tasks

[Feature]: useAsyncQueue #584

haejunejung opened this issue Nov 16, 2024 · 16 comments
Assignees
Labels
feature 새로운 기능 추가 @modern-kit/react @modern-kit/react

Comments

@haejunejung
Copy link
Contributor

haejunejung commented Nov 16, 2024

Package Scope

  • 기존 패키지에 기능 추가
  • 새로운 패키지
  • Package name: @modern-kit/react/hooks

Overview

비동기 함수를 순차적으로 실행할 수 있는 커스텀 훅을 만드는 건 어떨지 의견을 묻고자 ISSUE 남깁니다.

간단 기능 소개

비동기 함수를 실행시켜야 하되, 순차적으로 실행되기를 바라는 경우가 가끔 있는데요.

예를 들어, 결제 과정을 처리할 때, 결제 정보 검증 -> 결제 승인 -> 영수증 발행처럼 각 과정은 비동기로 처리하고 싶으나 그 과정이 순차적으로 진행되어야 하는 경우에 쉽게 처리할 수 있는 훅이 있으면 좋지 않을까 생각해봤습니다.

현재 생각한 것은 tasks와 options를 props로 전달했을 때, tasks를 처리해주는 것으로 생각했습니다. 간단하게 구현해본 건 아래와 같은 형태로 생각했습니다.

import { useState, useEffect } from 'react';
function noop() {}

type UseAsyncQueueTask<T> = (...args: any[]) => T | Promise<T>;

type QueueTasks<T extends any[]> = {
  [K in keyof T]: UseAsyncQueueTask<T[K]>;
};

interface UseAsyncQueueResult<T> {
  state: "aborted" | "fulfilled" | "pending" | "rejected";
  data: T | null;
}

interface UseAsyncQueueOptions {
  onError?: () => void;
  onFinished?: () => void;
}

interface UseAsyncQueueReturn<T> {
  result: T; 
}

export default function useAsyncQueue<T extends any[], S = QueueTasks<T>>(
  tasks: S & Array<UseAsyncQueueTask<any>>,
  options: UseAsyncQueueOptions = {}
): UseAsyncQueueReturn<{ [P in keyof T]: UseAsyncQueueResult<T[P]> }> {
  const {
    onError = noop,
    onFinished = noop,
  } = options;

  const promiseState: Record<
    UseAsyncQueueResult<T>["state"],
    UseAsyncQueueResult<T>["state"]
  > = {
    aborted: "aborted",
    fulfilled: "fulfilled",
    pending: "pending",
    rejected: "rejected",
  };

  const defaultValue = tasks.map(() => ({
    state: promiseState.pending,
    data: null,
  })) as { [P in keyof T]: UseAsyncQueueResult<T[P]> };

  const [result, setResult] =
    useState<{ [P in keyof T]: UseAsyncQueueResult<T[P]> }>(defaultValue);


  useEffect(() => {
    if (tasks.length === 0) {
      onFinished();
      return;
    }

    const executeTasks = async () => { 
      // 각 task 실행, result에 결과값 저장 
    };

    executeTasks();
  }, []);

  return { result };
}

아래와 같은 상황에서 쓸 수 있지 않을까 싶습니다.

import { useAsyncQueue } from "./useAsyncQueue"; // 훅 import

const PaymentComponent = ({ paymentData }) => {
  // 결제 프로세스를 순차적으로 정의
  const tasks = [
    () => validatePaymentInfo(paymentData), // 결제 정보 검증
    () => processPayment(paymentData), // 결제 승인
    () => generateReceipt(paymentData), // 영수증 발행
  ];

  const { result } = useAsyncQueue(tasks, {
    onError: () => alert("결제 처리 중 오류가 발생했습니다."),
    onFinished: () => alert("결제 처리 완료!"),
  });

  return (
    <div>
      {result.map((task, index) => (
        <div key={index}>
          {task.state === "pending" && <span>결제 ...</span>}
          {task.state === "fulfilled" && <span>결제 완료</span>}
          {task.state === "rejected" && <span>결제 실패</span>}
        </div>
      ))}
    </div>
  );
};
@ssi02014
Copy link
Contributor

ssi02014 commented Nov 17, 2024

@haejunejung 비동기 함수를 순차적으로 사용하기 위한 useAsyncProcessQueue 유사한 훅이 현재 존재합니다만.... 네이밍은 유지하고, 이쪽을 개선해 볼 수 있을까요?

현재는 아래와 같이 활용 할 수 있습니다만... 사용 방식이라던가 사용성이 부족하고, 사용 케이스가 적은 것 같습니다.
제안 주신 로직으로 기존 훅을 더욱 개선 할 수 있습니다.

const { addToProcessQueue } = useAsyncProcessQueue();

addToProcessQueue(promiseCallback1);
addToProcessQueue(promiseCallback2);

제가 느끼기에는 제안 주신 로직이 각각 tasks에 대한 상태를 관리할 수 있어 좋아보이네요
사용성 개선이 분명히 있다면 과감하게 내부 로직을 변경 할 수 있습니다!

몇 가지 의견을 드리고 싶습니다 고려해주시면 감사드립니다.

  1. UseAsyncQueueTask 타입은 훅의 목적에 맞게 Promise만 반환하는 것에 어떻게 생각하실까요?? 좀 더 범용적으로 사용하기 위해 T | Promise<T>을 열어두는게 더 나을까요? 🤔
  • 추가적으로 UseAsyncQueueTask 타입 네이밍의 suffix에 붙은 use를 빼도 좋을 것 같은데 어떻게 생각하실까요?
type AsyncQueueTask<T> = (...args: any[]) => Promise<T>; // ?
type AsyncQueueTask<T> = (...args: any[]) => T | Promise<T>; // ?
  1. UseAsyncQueueOptions 전체 task가 완료 됐을 때 호출 할 onSuccess도 있으면 좋을 것 같습니다. 🤗
interface UseAsyncQueueOptions {
  onSuccess:? () => void;
  onError?: () => void;
  onFinished?: () => void;
}
  1. 2번의 연장선으로 전체 task에 대한 loading 상태가 추가로 관리되면 좋을 것 같습니다!
  • 추가로 다른 훅과의 네이밍을 맞추기 위해 {~~}ReturnType으로 네이밍을 변경합니다.
interface UseAsyncQueueReturnType<T> {
  result: T; 
  isLoading: boolean;
}
  1. promiseState는 훅의 리렌더링에 영향을 받지 않게 훅 외부에 정의하고 사용해도 좋을 것 같습니다.
const promiseState = ...

useAsyncQueue() {
  // ...
} 
  1. 반복해서 사용되는 { [P in keyof T]: UseAsyncQueueResult<T[P]> } 타입도 재사용할 수 있게 정의해서 사용해도 좋을 것 같습니다.
  • 아래 코멘트에서 디테일하게 의견을 남겼습니다.

위 의견을 기반으로 아래 코멘트에 디테일한 의견을 남겼습니다! 검토부탁드립니다
지속적으로 modern-kit에 관심가져주셔서 감사합니다 😊👍

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 17, 2024

@haejunejung cc. @Sangminnn
최종적으로 제안드리는 인터페이스는 아래와 같습니다.
주석으로 의도를 남겼습니다. 검토부탁드립니다 😄

// (*) State 추출
type State = 'aborted' | 'fulfilled' | 'pending' | 'rejected';

// (*) 'use' suffix 제거, T를 제거할 지 말지 고민 필요
type AsyncQueueTask<T> = (...args: any[]) => T | Promise<T>;

// (*) 'use' suffix 제거 및 QueueTasks -> AsyncQueueTasks 네이밍 변경
// any[] 타입을 readonly any[] 로 변경, 이유는? as const 처리된 배열 요소도 허용하기 위함
// 이후 모든 any[]를 readonly any[]로 활용
type AsyncQueueTasks<T extends readonly any[]> = {
  [K in keyof T]: AsyncQueueTask<T[K]>;
};

// (*) 'use' suffix 제거
interface AsyncQueueResult<T> {
  state: State;
  data: T | null;
}

// (*) onSuccess 추가
interface UseAsyncQueueOptions {
  onError?: () => void;
  onFinished?: () => void;
  onSuccess?: () => void;
}

// 1. 네이밍을 다른 훅이랑 통일화 하기 위해 {~~~}ReturnType 으로 변경
// 2. T -> T extends readonly any[] 로 변경, 이유는? { [P in keyof T]: AsyncQueueResult<T[P]> } 중복 코드 개선를 여기서 개선
// 3. 통일성 있게 P를 K로 변경
// 4. 전체 task에 대한 isLoading 추가
interface UseAsyncQueueReturnType<T extends readonly any[]> {
  result: { [K in keyof T]: AsyncQueueResult<T[K]> };
  isLoading: boolean;
}

// (*) 훅 외부로 추출
// 더욱 명확하게 관리하기 위해 Record 타입 제거 및 as const 추가
// 네이밍 상수로서 관리하기 때문에 스네이크 케이스로 변경
const PROMISE_STATE = {
  aborted: 'aborted',
  fulfilled: 'fulfilled',
  pending: 'pending',
  rejected: 'rejected',
} as const;

// (*) S 제네릭 제거 및 tasks 타입 변경
// 이 내용 이유는 아래에 추가로 남겨놓겠습니다.
export function useAsyncQueue<T extends readonly any[]>(
  tasks: AsyncQueueTasks<T>,
  options: UseAsyncQueueOptions = {}
): UseAsyncQueueReturnType<T> {
  // (*) UseAsyncQueueReturnType<T>['result']; 활용으로
  // { [P in keyof T]: AsyncQueueResult<T[P]> } 반복 코드 제거
  const defaultValue = tasks.map(() => ({
    state: PROMISE_STATE.pending,
    data: null,
  })) as UseAsyncQueueReturnType<T>['result']; // (*)

  const [result, setResult] =
    useState<UseAsyncQueueReturnType<T>['result']>(defaultValue); // (*)
}

useAsyncQueue의 S제네릭 타입 제거와 tasks 타입 변경 이유는?

  • 아래와 같이 사용한다고 가정했을 때
  const validatePaymentInfo = async () => 1; // number
  const processPayment = async () => true; // boolean
  const generateReceipt = async () => '2'; // string

  // const tasks: (
  //   | (() => Promise<number>)
  //   | (() => Promise<string>)
  //   | (() => Promise<boolean>)
  // )[];
  const tasks = [
    () => validatePaymentInfo(), // 결제 정보 검증
    () => processPayment(), // 결제 승인
    () => generateReceipt(), // 영수증 발행
  ];

기존에는 result타입이 UseAsyncQueueResult<any>[] 로 타입이 추론됩니다.

// as-is
// type result: UseAsyncQueueResult<any>[]
  const { result } = useAsyncQueue(tasks, {
    onError: () => alert('결제 처리 중 오류가 발생했습니다.'),
    onFinished: () => alert('결제 처리 완료!'),
  });

개선 후 AsyncQueueResult<string | number | boolean> 로 더욱 명확한 타입 추론이 됩니다.

// to-be
// type result: AsyncQueueResult<string | number | boolean>
  const { result } = useAsyncQueue(tasks, {
    onError: () => alert('결제 처리 중 오류가 발생했습니다.'),
    onFinished: () => alert('결제 처리 완료!'),
  });

@ssi02014 ssi02014 added feature 새로운 기능 추가 @modern-kit/react @modern-kit/react labels Nov 17, 2024
@Sangminnn
Copy link
Collaborator

좋은 훅에 대한 논의가 있어 지나가면서 보게되었습니다!

처음 제안해주신 haejunejung님의 의견도 너무 좋았고 이에 대해 보완해주신 의견도 너무 좋아서 실제 구현이 기대되네요! 👍👍

저는 다른 부분은 전반적으로 좋아보이고 다만 새로 의견주신 onSuccess 추가에 대한 부분은 혹시 현재 프로토타입의 인터페이스에서는 onFinished 가 수행해주고있는게 아닐지 여쭤봅니다! 😄

  useEffect(() => {
    // 남은 작업이 없으면 onFinished 수행
    if (tasks.length === 0) {
      onFinished();
      return;
    }

    const executeTasks = async () => { 
      // 각 task 실행, result에 결과값 저장 
    };

    executeTasks();
  }, []);

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 17, 2024

@Sangminnn 오우 ㅎㅎ 저는 onFinished는 성공,실패와 별개로 모든 프로세스 종료되면 호출되는 예를 들어 finally와 같은 목적이라고 이해했습니다..! 😃
모두 성공했을 떄의 onSuccess, 하나라도 실패했을 때의 onError, 모든 프로세스 후 호출되는 onFinished 3가지 콜백이 주어지면 좋을 것 같은데 어떻게 생각하실까요! 추가적인 좋은 의견 주시면 감사드립니다! cc. @haejunejung

@Sangminnn
Copy link
Collaborator

Sangminnn commented Nov 18, 2024

@ssi02014 아하! 말씀해주신 부분도 이해했습니다! 처음에 haejunejung 님께서 제안해주신 인터페이스를 기준으로 생각하다보니 말씀해주신 status를 잠시 놓쳤네요! 해당 Status를 두는 것에는 저도 동의합니다! 👍

추가적으로 한가지 고민인 부분은 aborted 에 대한 상태가 꼭 필요할까? 라는 부분이었습니다. Promise를 다루기에 abort에 대한 접근을 한 것은 너무 좋았으나, 실제로 해당 훅의 역할은 순차적으로 수행하고자 하는 여러 Date Fetching에 대해 기존에 async-await와 같이 명령적으로 사용하는 것이 아닌 선언적으로 사용하는 이점이 있는 훅이라고 생각했습니다.

Aborted 상태는 일반적으로 API 호출 플로우 도중 이탈이 발생했을 경우 요청하던 API 를 취소하는데에 주 목적 이 �있고 일반적으로는 특정 이유로 인해 해당 컴포넌트 자체가 unmount되는 케이스정도일 것으로 생각이 되었는데요, 그렇다면 일반적인 케이스에서는 해당 훅을 사용하는 부모 컴포넌트가 unmount되어야 Abort가 발생하는데, useAsyncQueue를 사용하는 부모 컴포넌트가 unmount되지 않고 useAsyncQueue로 부터 반환해주는 abort state를 활용할 수 있는 케이스가 있을까? 라는 부분이 의문이었네요!

@haejunejung
Copy link
Contributor Author

@ssi02014
이슈에 대해서 많이 보완된 것 같아서 기분이 좋습니다 👍
저도 onSuccess, onError, onFinsihed 세 가지 Status로 하는 것이 좋아보입니다.제가 미처 생각해보지 못했던 부분이네요. 그리고, 보완된 부분은 전부 납득이 가는 내용이었습니다!

@Sangminnn
Sangminn님이 말씀해주신 aborted 상태에 대해서 고민해봤는데요. 처음에 인터페이스를 만들 때는 특정 Promise에 대한 aborted가 전달될 때, 특정한 행동을 추가로 할 수도 있지 않을까? 혹은 디버깅에 대한 용도로 사용될 수 있지 않을까? 를 고민하며 넣어두었었습니다. 아래와 같은 상황을 고려했었습니다. 의견 부탁드립니다 🙏

const 컴포넌트 = () => {
  const { results, isLoading } = useAsyncQueue([fn1, fn2]);

  // 예시: 특정 작업이 취소된 경우 해야 하는 작업
  useEffect(() => {
    const abortedTask = results.find(result => result.state === 'aborted');
    if (abortedTask) {
      // 예를 들어, 다른 API를 호출하거나 UI를 업데이트하는 로직 추가 ??
    }
  }, [results]);
};

추가적인 의견은, aborted에 대해 고민하다보니, 전체 프로세스에 대한 중지를 하고 싶을 때도 있지 않을까? 라는 부분이었습니다. AbortController를 사용할 때를 고려하여, 인터페이스에서signal 옵션을 추가한다면 이를 효과적으로 다룰 수 있을 것 같아서 이 부분에 대해서도 의견 부탁드립니다. 🙏

// (*) signal 추가
interface UseAsyncQueueOptions {
  onError?: () => void;
  onFinished?: () => void;
  onSuccess?: () => void;
  signal?: AbortSignal
}

export function useAsyncQueue<T extends readonly any[]>(
  tasks: AsyncQueueTasks<T>,
  options: UseAsyncQueueOptions = {}
): UseAsyncQueueReturnType<T> {
  const defaultValue = tasks.map(() => ({
    state: PROMISE_STATE.pending,
    data: null,
  })) as UseAsyncQueueReturnType<T>['result']; // (*)

  const [result, setResult] =
    useState<UseAsyncQueueReturnType<T>['result']>(defaultValue); // (*)

  // 만약 비동기 실행 중에 `signal?.abort`가 `true`가 될 때,
  // 남아있는 모든 작업을 `aborted`로 처리한다. 
}

아래는 활용 예시입니다!

const 컴포넌트 = () => {
  const [abortController] = useState(new AbortController()); // AbortController 생성

  const tasks = [fn1, fn2];
  
  const { result, isLoading } = useAsyncQueue(tasks, {
    signal: abortController.signal, // signal을 전달
    onError: () => console.log('Error occurred'),
    onFinished: () => console.log('All tasks finished'),
    onSuccess: () => console.log('All tasks completed successfully'),
  });

  const handleCancel = () => {
    abortController.abort(); // 갑작스럽게 결제 취소와 같이 취소 요청이 필요하다면?
  };

  return (
    <div>
      <button onClick={handleCancel}>Cancel Request</button>
      {isLoading ? <Loading /> : <div>로딩끝</div>}
    </div>
  );
};

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 19, 2024

@haejunejung cc. @haejunejung

Sangminn님이 말씀해주신 aborted 상태에 대해서 고민해봤는데요. 처음에 인터페이스를 만들 때는 특정 Promise에 대한 aborted가 전달될 때, 특정한 행동을 추가로 할 수도 있지 않을까? 혹은 디버깅에 대한 용도로 사용될 수 있지 않을까? 를 고민하며 넣어두었었습니다. 아래와 같은 상황을 고려했었습니다. 의견 부탁드립니다 🙏

저는 좋습니다. 일반적으로 자주 사용하는 사용 케이스만 따졌을 때는 'fulfilled' | 'pending' | 'rejected' 만으로도 충분하다고 느끼지만,
aborted까지 추가된 Promise 진행 상태에 대해 더욱 명확하게 제공 할 수 있다면 그것대로 좋다고 느껴집니다.


AbortController를 사용할 때를 고려하여, 인터페이스에서 signal 옵션을 추가한다면 이를 효과적으로 다룰 수 있을 것 같아서 이 부분에 대해서도 의견 부탁드립니다. 🙏

위 내용과 연장선으로 aborted 상태를 관리하려면 해당 signal을 받을 수 밖에 없을 것 같습니다. 또한, 저 역시 사용자는 특정 상황에서 특정 비동기 작업 이후 모든 프로세스를 종료 할 수 있는 기능을 제공하면 좋을 것 같다고 생각합니다. 이를 통해 개발자 경험(DX)을 개선 할 수 있다고 생각합니다.

VusUse useAsyncQueue 케이스 처럼 abortController.signal을 훅 props로 전달받고, 외부에서 abortController.abort()가 호출됐을 때 signal의 aborted 값을 바탕으로 abortController.abort() 호출 시점 이후 비동기 함수 호출은 하지 않고, results 상태만을 abort 처리하면 될 것 같습니다.

  • 외부에서 받아온 tasks의 각 비동기 함수들에 각각의 signal을 주입하는 것은 제한적이기 때문에 개별 처리는 힘들고, 전체 프로세스에 대해서만 처리할 수 있을 것 같습니다.
    • 예를 들어, 만약 [1, 2, 3, 4, 5] 호출 중에 2 비동기 진행 시점에 abort를 호출하면 2 이후 프로세스가 모두 취소(3부터)가 됩니다.
    • 아래는 대충 구상한 내용입니다. (디테일한 내용은 추가 필요)
const executeTasks = async (index: number = 0) => {
  console.log("before");

  for (let i = index; i < tasks.length; i++) {
    if (signal.aborted) {
      console.log("aborted 상태 처리");
      // results 상태만 처리하고 이후 실제 fetch라던가 비동기 task 호출 X
      continue;
    }
    const task = tasks[i];
    const res = await task();
    console.log(res);
  }

  console.log("after");
};
const abortController = new AbortController(); 
const tasks = [1, 2, 3, 4, 5]; // 1~5 특정 비동기 함수

const cancel = () => {
 abortController.abort();
}

// 2가 진행되는 시점에 cancel 호출
cancel();
// 3부터 abort 처리

추가적인 의견

  1. 최근에 완료된 Promise의 result를 반환하는 것에 어떻게 생각하실까요?
  • 현재는 result로 모든 Promise에 대한 데이터를 배열로서 다루고 있지만, 최근에 처리가 완료된 Promise result에 대한 개별 아이템에 대한 반환 데이터가 있다면 좋을 것 같다는 생각입니다.
  • 네이밍은 currentResult 정도면 충분하다고 느껴지긴 했습니다.
  • 기존 result 배열 네이밍은 results로 변경
// 내부적으로 currentIndex를 관리
const currentIndex = useRef(-1);

return {
  // ...
  currentResult: results[currentIndex]
}
  1. enabled 옵션이 포함되면 좋을 것 같습니다. 누군가한테는 특정 상황에 바로 훅이 호출해선 안될 수도 있습니다. 이를 직접 컨트롤 할 수 있으면 좋을 것 같습니다.like. useQuery enabled
interface UseAsyncQueueOptions {
  onError?: () => void;
  onFinished?: () => void;
  onSuccess?: () => void;
  signal?: AbortSignal;
  enabled?: boolean; (*) 추가
}
useEffect(() => {
  if (!enabled) return; // 호출 X
  // ...
}, [/* ... */])
  1. 특정 index부터 프로세스를 재호출하는 retry 기능이 추가되면 좋을 것 같습니다. 그러기 위해 exctueTasks는 index를 받아올 수 있어야합니다. 기본 값은 0 (전체 프로세스)
// usePreservedCallback로 참조 유지해 useEffect 내에서의 사이드이펙트 방지
const executeTasks = usePreservedCallback(async (index: number = 0) => { 
  // 각 task 실행, result에 결과값 저장 
});

// 네이밍에 따른 목적에 맞게 retryTasks로 정의 후 활용
const retryTasks = useCallback((index: number) => {
 executeTasks(index);
}, [executeTasks]);

useEffect(() => {
  if (tasks.length === 0) {
    onFinished();
    return;
  }

  executeTasks(0);
  }, [executeTasks]);

return { 
  // ...
  retryTasks,
}
retryTasks(2); // tasks의 인덱스 2부터 차례대로 호출

// 전체 프로세스에 대한 retry도 가능
retryTasks(0);

// 아래와 같이 retryAll을 정의해서 활용하는 케이스도 OK입니다만..
// retryTasks만으로도 충분해보입니다 :) 
const retryAll = useCallback(() => {
  executeTasks(0);
}, []);

return {
  // ...
  retryTasks,
  retryAll,
}

추가 적인 의견 남겨주시면 감사드립니다 :)

@Sangminnn
Copy link
Collaborator

@haejunejung @ssi02014 먼저 두분 다 좋은 의견과 상세한 설명을 해주셔서 감사합니다! 👍

haejunejung 님께서 말씀해주신 부분의 의도도 이해했고, 제가 처음에 우려했던 부분에 대해서는 ssi02014님께서 잘 말씀해주시면서도 해결방법을 제시해주신것같아요!

저는 처음에 각 API에 대한 중단을 수행하기 위해서는 개별 signal을 가지고 API마다 전부 넣어주어야하며, 부득이하게 hook의 사용처가 unmount되는 경우 AbortController의 역할인 수행중인 API의 중단이라는 역할이 불가능해진다고 생각했습니다.

ssi02014님께서 언급해주신

VusUse useAsyncQueue 케이스 처럼 abortController.signal을 훅 props로 전달받고, 외부에서 abortController.abort()가 호출됐을 때 signal의 aborted 값을 바탕으로 abortController.abort() 호출 시점 이후 비동기 함수 호출은 하지 않고, results 상태만을 abort 처리하면 될 것 같습니다.

이 맥락처럼 모든 API의 수행 도중 중단하겠다는 의도를 버리고, 훅 내에서 비정상적인 종료로 인한 중단을 배제하면서 의도적으로 사용처에서 흐름을 중단할 수 있는 방향이라면 저도 공감합니다! 👍

@haejunejung
Copy link
Contributor Author

@ssi02014
상세한 설명 감사합니다! 👍 해당 useAsyncQueue를 레퍼런스로 활용한 것 맞아요 ㅎㅎ... 점점 더 보완되는게, 해당 훅보다 실제로 사용할 수 있는 훅으로 되어 가고 있는 것 같아서 좋네요 😆

추가적인 의견

  1. 최근에 완료된 Promise의 result를 반환하는 것에 어떻게 생각하실까요?
  • 현재는 result로 모든 Promise에 대한 데이터를 배열로서 다루고 있지만, 최근에 처리가 완료된 Promise result에 대한 개별 아이템에 대한 반환 데이터가 있다면 좋을 것 같다는 생각입니다.
  • 네이밍은 currentResult 정도면 충분하다고 느껴지긴 했습니다.
  • 기존 result 배열 네이밍은 results로 변경

개별 아이템에 대해서 반환을 다룬다는 점은 좋은 것 같습니다. 다만, currentResult가 어떤 index에 해당하는 지를 알기 위해서는 currentResultcurrentIndex를 모두 필요로 하는데, currentIndex를 반환하도록 해서 사용자가 resultscurrentIndex를 활용할 수 있도록 하는 방안은 어떤가요?

(개인적인 견해로는 currentIndex와 results를 사용하는 방안이 사용자가 더 의도를 가지고 사용할 것 같아서요!)

AS-IS

const { currentResult, currentIndex, results } = useAsyncQueue(...);
// currentResult가 어떤 인덱스에서 실행되는지를 
// currentIndex를 통해 한 번 더 알아야 함.
// 의식의 흐름이 currentResult -> currentIndex -> currentResult

TO-BE

const { currentIndex, results } = useAsyncQueue(...);
// 사용자가 results[currentIndex]로 알 수 있도록 한다면
// 의식의 흐름이 currentIndex -> currentResult
  1. enabled 옵션이 포함되면 좋을 것 같습니다. 누군가한테는 특정 상황에 바로 훅이 호출해선 안될 수도 있습니다. 이를 직접 컨트롤 할 수 있으면 좋을 것 같습니다.like. useQuery enabled

enabled 옵션이 있는게 더 좋을 것 같습니다. 단순히 컴포넌트 시작점에서만 사용하는게 아니라 의도를 가지고 실행한다는 점이 좋은 것 같아요 👍

  1. 특정 index부터 프로세스를 재호출하는 retry 기능이 추가되면 좋을 것 같습니다. 그러기 위해 exctueTasks는 index를 받아올 수 있어야합니다. 기본 값은 0 (전체 프로세스)

특정 index부터 프로세스를 재호출한다는 방법은 굉장히 좋은 것 같아요 👍. 다만, retry가 전체 프로세스를 재호출한다는 느낌을 주는 것 같아서, 전체 프로세스를 재호출하는 retry와 중간부터 다시 시작할 수 있는 resume은 어떤가요?

retry() // 전체 프로세스 재호출
resume(1) // 1번 인덱스부터 재호출

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 20, 2024

@haejunejung

currentResult와 currentIndex를 모두 필요로 하는데, currentIndex를 반환하도록 해서 사용자가 results와 currentIndex를 활용할 수 있도록 하는 방안은 어떤가요?

넵 좋은 의견이라고 생각합니다 동의합니다! :)

특정 index부터 프로세스를 재호출한다는 방법은 굉장히 좋은 것 같아요 👍. 다만, retry가 전체 프로세스를 재호출한다는 느낌을 주는 것 같아서, 전체 프로세스를 재호출하는 retry와 중간부터 다시 시작할 수 있는 resume은 어떤가요?

저는 retry가 오히려 "전체"의 의미가 포함되어 있지 않다보니 전체 프로세스를 재호출한다에 어색함이 느껴지는 것 같습니다. 전체의 의미가 네이밍에 포함되면 좋을 것 같습니다

사실 최초에 의견드렸던 retryTasks의 인자로 index를 받는다면 인터페이스를 통해 사용자가 충분히 해당 함수 동작에 대해서 유추할 수 있을 것으로 생각이 들긴하지만, 이 부분이 어색하시다면 retryAll, resume 형태도 좋다고 생각합니다.

즉, retryAll/retryTasks, retryAll/resume 둘 다 괜찮아보입니다. @haejunejung @Sangminnn 네이밍 관련해서 추가 있으시면 말씀주시면 감사드립니다!

@Sangminnn
Copy link
Collaborator

저는 currentIndex와 retryAll/resume 이 전체 재실행과 재시도의 차이를 드러내기에 더 적합해보이네요! 👍

@haejunejung
Copy link
Contributor Author

@ssi02014
저도 retryAll/resume이 더 적합해보이네요! 단순히 retry보다 더 직관적으로 보이네요 👍

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 20, 2024

@Sangminnn @haejunejung 어느 정도 큰 틀을 잡힌 것 같습니다.🚀 개발 전 논의에 참여해주셔서 감사드립니다! 🙇🏻‍♂️
우선 논의한대로 진행을 하고, 추후에 필요한게 있다고 느껴진다면 그때 추가적인 고도화를 진행하면 좋을 것 같습니다

@haejunejung
Copy link
Contributor Author

haejunejung commented Nov 20, 2024

@ssi02014 @Sangminnn
이슈에 대해서 심도있게 봐주셔서 감사합니다 😄
제가 작업을 진행해도 괜찮을까요? 👀

@ssi02014
Copy link
Contributor

ssi02014 commented Nov 20, 2024

@haejunejung 넵 좋습니다 ! modern-kit에 관심가져주셔서 감사드립니다 🙇🏻‍♂️
진행하면서 문제가 있거나 혹은 논의가 필요한 점이 있다면 추가로 코멘트 남겨주시면 감사드립니다.

@Sangminnn
Copy link
Collaborator

@haejunejung @ssi02014 좋은 훅 제안해주셔서 감사드리고 좋은 의견 주셔서 많이 배워갑니다 :) 논의하시느라 고생하셨습니다! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 새로운 기능 추가 @modern-kit/react @modern-kit/react
Projects
None yet
Development

No branches or pull requests

3 participants