본문 바로가기

FrontEnd

[번역] Vue3 Coding Better Composables : await 없이 비동기를 처리하는 컴포저블 잘만들기

반응형

await 없이 비동기를 처리는 컴포저블을 만들어 봅시다.

해당 글은 컴포저블 잘 만들기 시리즈의 마지막 아티클입니다.

아래 글의 번역입니다.

https://www.vuemastery.com/blog/coding-better-composables-5-of-5/

 

Coding Better Composables: Async Without Await (5/5) | Vue Mastery

This tutorial series will serve as your guide on how to craft solid composables that you and your team can rely on.

www.vuemastery.com

이전 시리즈 보기

2022.12.08 - [Vue.js] - [번역] Vue3 Coding Better Composables : 옵션 객체 파라미터 활용하여 컴포저블 잘 만들기

 

[번역] Vue3 Coding Better Composables : 옵션 객체 파라미터 활용하여 컴포저블 잘 만들기

컴포저블을 잘 활용하여 클린한 Vue3 코드를 작성해 봅시다. 해당 게시물에서는 파라미터를 잘 설계하는 방법을 다룹니다. 컴포저블이란? 상태 저장 로직을 캡슐화하고 재사용하기 위해 Vue Composi

itchallenger.tistory.com

2022.12.08 - [Vue.js] - [번역] Vue3 Coding Better Composables : ref 및 unref를 사용하여 유연한 컴포저블 만들기

 

[번역] Vue3 Coding Better Composables : ref 및 unref를 사용하여 유연한 컴포저블 만들기

prop의 ref 여부와 관계없이 유연하게 사용할 수 있는 컴포저블을 만들어 봅시다. 해당 글을 번역 및 요약한 글입니다. https://www.vuemastery.com/blog/coding-better-composables-2-of-5/ Coding Better Composables: Flexible

itchallenger.tistory.com

2022.12.08 - [Vue.js] - [번역] Vue3 Coding Better Composables : 다형적인 리턴값을 활용하여 컴포저블 잘만들기

 

[번역] Vue3 Coding Better Composables : 다형적인 리턴값을 활용하여 컴포저블 잘만들기

컴포저블의 리턴 값을 좀 더 유용하게 만드는 방법을 알아봅니다. 해당 글을 번역 및 요약한 글입니다. https://www.vuemastery.com/blog/coding-better-composables-3-of-5/ Coding Better Composables: Dynamic Returns (3/5) | Vu

itchallenger.tistory.com

2022.12.09 - [Vue.js] - [번역] Vue3 Coding Better Composables : 인터페이스를 잘 설계하여 컴포저블 잘 만들기

 

[번역] Vue3 Coding Better Composables : 인터페이스를 잘 설계하여 컴포저블 잘 만들기

컴포저블용 인터페이스 작성에 대해 알아보고 이 프로세스가 먼 미래를 위해 확장 가능한 컴포저블을 만드는 데 어떻게 도움이 되는지 알아봅니다. 해당 문서의 번역입니다 : https://www.vuemastery.c

itchallenger.tistory.com


요약 :

  • 컴포저블은 ref만 리턴하고, ref 로직만 조합함
  • promise 로직은 컴포저블 내부로 캡슐화

Await 없이 비동기 처리하기

Composition API로 비동기 로직을 작성하는 것은 때때로 까다로울 수 있습니다.

모든 비동기 코드는  모든 setup 함수 내 반응성 코드 이후에 나와야 합니다.

 

setup 함수는 await 문을 실행한 후 리턴합니다.
리턴하면 컴포넌트가 마운트되고 응용 프로그램은 평소대로 계속 실행됩니다.
computed prop, watcher 또는 다른 것이든 await 후에 정의된 모든 반응성 객체는 아직 제대로 초기화되지 않은 상태입니다.

즉, await 후에 정의된 계산된 속성은 처음에는 템플릿에서 사용할 수 없습니다.
비동기 코드가 완료되고 setup 함수가 실행을 완료한 후에만 존재합니다.

그러나 이러한 문제 없이 어디에서나 사용할 수 있는 비동기 컴포넌트를 작성하는 방법이 있습니다.
const count = ref(0);
// This async data fetch won't interfere with our reactivity
const { state } = useAsyncState(fetchData());
const doubleCount = computed(() => count * 2);

이 패턴은 비동기 코드 작업을 훨씬 더 안전하고 간단하게 만듭니다.
머릿속에서 추적해야 하는 항목의 양을 줄이는 것은 항상 도움이 됩니다!

 


Await 없이 비동기 처리 패턴 구현

1. 먼저 상태를 준비하고 반환합니다.

아직 값이 무엇인지 모르기 때문에 null 값으로 초기화합니다.

export default useMyAsyncComposable(promise) {
  const state = ref(null);
  return state;
}

2. Promise을 기다린 다음 결과를 상태 참조로 설정하는 메서드를 만듭니다.

const execute = async () => {
  state.value = await promise;
}

Promise가 resolve될 때마다 반응적으로 상태를 업데이트합니다.

 

3. 2번에서 만든 함수를 컴포저블에 추가하기만 하면 됩니다.

export default useMyAsyncComposable(promise) {
  const state = ref(null);

  // Add in the execute method...
  const execute = async () => {
    state.value = await promise;
  }

  // ...and execute it!
  execute();

  return state;
}

useMyAsyncComposable 메서드에서 반환되기 직전에 실행 함수를 호출합니다.
그러나 await 키워드는 사용하지 않습니다.

 

excute 함수 내에서 실행을 중지하고 promise의 resolve를 기다리면

실행 흐름이 즉시 useMyAsyncComposable 함수로 반환됩니다.
로직 실행이 execute() 문을 지나서 계속되고 컴포저블에서 리턴합니다.

 

흐름에 대한 자세한 설명은 다음과 같습니다.
export default useMyAsyncComposable(promise) {
  const state = ref(null);

  const execute = async () => {
    // 2. 프로미스의 resolve 기다림
    state.value = await promise

    // 5. 잠시 후
    // 프로미스가 resolve 되고 상태가 반응적으로 업데이트됨
  }

  // 1. `execute` 함수 실행
  execute();
  // 3. `await` 은 실행 흐름을 반환함

  // 4. 샹태를 리턴하고 setup 함수 내 로직을 계속해서 실행함
  return state;
}

promise는 "백그라운드에서" 실행되며 우리는 해당 promise를 await 하지 않기 때문에 setup 함수의 흐름을 방해하지 않습니다.
반응성을 방해하지 않고 이 컴포저블을 어디에나 배치할 수 있습니다.

 

이제 멋진 구현이 있는 또 다른 컴포저블을 살펴보겠습니다.

useAsyncState

useAsyncState은 지금까지 설명한 예제의 좀 더 세련된 버전입니다.

이를 통해 원하는 곳 어디에서나 비동기 메서드를 실행할 수 있고 반응적으로 업데이트된 결과를 얻을 수 있습니다.
const { state, isLoading } = useAsyncState(fetchData());

소스코드를 보면(looking at the source code) 위의 예제와 비슷하지만, 더 많은 엣지 케이스 처리를 구현한 것을 볼 수 있습니다.

간략한 버전입니다.

export function useAsyncState(promise, initialState) {
  const state = ref(initialState);
  const isReady = ref(false);
  const isLoading = ref(false);
  const error = ref(undefined);

  async function execute() {
    error.value = undefined;
    isReady.value = false;
    isLoading.value = true;

    try {
      const data = await promise;
      state.value = data;
      isReady.value = true;
    }
    catch (e) {
      error.value = e;
    }

    isLoading.value = false;
  }

  execute();

  return {
    state,
    isReady,
    isLoading,
    error,
  };
}

이 컴포저블은 또한 데이터를 가져온 시점을 알려주는 isReady를 반환합니다.
또한 isLoading ref와 error ref를 반환하여
로딩 및 오류 상태를 추적할 수 있습니다.


useAsyncQueue

useAsyncQueue에 promise을 리턴하는 함수 배열을 제공하면 각 함수가 순서대로 실행됩니다.
그러나 다음 작업을 시작하기 전에 이전 작업이 완료될 때까지 기다리면서 순차적으로 수행합니다.
더 유용하게 만들기 위해 한 작업의 결과를 다음 작업의 입력으로 전달합니다.
// This `result` will update as the tasks are executed
const { result } = useAsyncQueue([getFirstPromise, getSecondPromise]);

다음은 공식 문서를 기반으로 한 예입니다.

const getFirstPromise = () => {
  // Create our first promise
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000);
    }, 10);
  });
};

const getSecondPromise = (result) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000 + result);
    }, 20);
  });
};

const { activeIndex, result } = useAsyncQueue([
  getFirstPromise,
  getSecondPromise
]);

코드를 비동기식으로 실행하더라도 await를 사용할 필요는 없습니다.
내부적으로도 컴포저블은 await을 사용하지 않습니다.
대신 우리는 이러한 Promise를 "백그라운드에서" 실행하고 결과가 반응적으로 업데이트되도록 합니다.
 

이 컴포저블이 어떻게 동작하는지 살펴보겠습니다. Async Without Await 패턴을 구현하기 위해

이 컴포저블은 먼저 반환될 activeIndex 및 result 값을 설정합니다(first hooks up).

// Default state values that can be updated reactively
const initialResult = Array.from(new Array(tasks.length), () => ({
  state: promiseState.pending,
  data: null,
});

// Make the reactive version that we'll return
const result = reactive(initialResult);

// Also set up the active index as a ref
const activeIndex = ref(-1);

그러나 주요 기능은 각 함수를 하나씩 실행(works through each function)하는 reduce에 의해 구동됩니다.

tasks.reduce((prev, curr) => {
  return prev.then((prevRes) => {
    if (result[activeIndex.value]?.state === promiseState.rejected && interrupt) {
      onFinished();
      return;
    }

    return curr(prevRes).then((currentRes) => {
      updateResult(promiseState.fulfilled, currentRes);
      activeIndex.value === tasks.length - 1 && onFinished();
      return currentRes;
    })
  }).catch((e) => {
    updateResult(promiseState.rejected, e);
    onError();
    return e;
  })
}, Promise.resolve());

리듀스 함수는 약간 복잡할 수 있으므로 분해하여 이해해 봅시다.
먼저 resolved된 promise로 전체 체인을 시작합니다.

tasks.reduce((prev, curr) => {
  // ...
}, Promise.resolve());

그 다음 각 작업 처리를 시작합니다.

이전 Promise에서 .then을 연결하여 이를 수행합니다.

Promise가 거부된 경우 조기에 중단하고 반환할 수 있습니다.

// Check if our last promise was rejected
if (result[activeIndex.value]?.state === promiseState.rejected && interrupt) {
  onFinished();
  return;
}

일찍 중단하지 않는다면, 이전 약속의 결과를 전달하여 다음 작업을 실행합니다.
또한 updateResult 메서드를 호출하여 이 컴포저블이 반환하는 result 배열에 반응적으로 추가합니다.

// Execute the next task with the result from the previous task
return curr(prevRes).then((currentRes) => {
  updateResult(promiseState.fulfilled, currentRes);
  activeIndex.value === tasks.length - 1 && onFinished();
  return currentRes;
});

보시다시피 이 컴포저블은 Async Without Await 패턴을 구현하지만 이 패턴은 전체 컴포저블의 몇 줄에 불과합니다.
따라서 많은 추가 작업이 필요하지 않으며 excute 역할을 하는 async 함수가 필요하다는 것만 기억하면 됩니다.


마무리

Async Without Await 패턴을 사용하면 훨씬 더 쉽게 비동기 컴포저블을 사용할 수 있습니다.
이 패턴을 사용하면 반응성 중단에 대한 걱정 없이 원하는 곳에 비동기 코드를 배치할 수 있습니다.

 

기억해야 할 핵심 원칙은 다음과 같습니다.
반응성 상태를 먼저 연결하면 원할 때마다 업데이트할 수 있으며 반응성으로 인해 값이 앱을 통해 흐릅니다.
그러면 await을 사용할 필요가 없습니다

 

이 기사는 시리즈의 끝입니다.

지금까지 다음과 같은 주제를 다루었습니다.

 

  1. 옵션 객체 파라미터를 사용하여 컴포저블을 더 합성 가능하게 만드는 방법
  2. ref 및 unref를 사용하여 인수를 보다 유연하게 만들기
  3. return 값을 더 유용하게 만드는 간단한 방법
  4. 인터페이스 먼저 생각하면 컴포저블이 더 강력해지는 이유
  5. await 없이 비동기 코드를 사용하는 방법 - 코드를 이해하기 쉽게 만들기

 


추가로 보면 좋은 글

vue3와 immer 사용하기

https://twitter.com/youyuxi/status/1493146218142208001?lang=en 

 

트위터에서 즐기는 Evan You

“Here's how easy it is to use immutable data via Immer in Vue 3. Live playground: https://t.co/Y3mzw0bEHZ”

twitter.com

shallowRef로 최적화

https://vuejs.org/api/reactivity-advanced.html#markraw

 

Reactivity API: Advanced | Vue.js

 

vuejs.org

 

반응형