observable

남예지·2022년 12월 13일
0

refreshToken을 하는 중에 나온 observable이라는 개념을 알아보자.

refreshToken을 만들어 쿠키에 저장하기 위해 프론트엔드에서 개발해 주어야 하는건
1. 토큰 만료 에러잡는 if문을 작성
2. restoreAccessToken에 refeshToken을 보내 refeshToken이 맞는지 확인하고 인가(복호화)성공하면 새로운 AccessToken을 만들어 다시 돌려준다. accesstoken을 새로운 accesstoken으로 바꿔주어야한다. (accesstoken 재발급)
3. accesstoken만 바꿔서 실패 쿼리 재시도

useEffect(() => {
    void aaa.toPromise().then((newAccessToken) => {
      setAccessToken(newAccessToken);
    });
  }, []);

  // 로그인 시 리프레시 토큰
  const errorLink = onError(({ graphQLErrors, operation, forward }) => {
    // 1. 에러를 캐치
    // 에러가 동시에 여러개가 들어올 수 있다. graphQLErrors  apollo docs에 나와있나
    if (graphQLErrors !== undefined) {
      for (const err of graphQLErrors) {
        // 1-2 해당 에러가 토큰만료 에러인지 체크하기(UNAUTHENTICATED)
        if (err.extensions.code === "UNAUTHENTICATED") {
          return fromPromise(
            // 2. refreshToken으로 accessToken을 재발급 받기 후 나주에 쓸 가능성이 높으므로 컴포넌트로 빼기
            getAccessToken().then((newAccessToken) => {
              setAccessToken(newAccessToken);

              // 3. 재발급 받은 accessToken으로 방금 실패한 쿼리의 정보 수정하기
              // 실패한 인가 operation 안에 getContext (헤더나 기타 정보를 가지고 있음
              if (typeof newAccessToken !== "string") return;
              operation.setContext({
                headers: {
                  ...operation.getContext().headers, // Authorization:  Bearer asldkfjlasdk => 만료된 토큰이 추가되어 있는 상태
                  Authorization: `Bearer ${newAccessToken}`, // 3-1 토큰만 새걸로 바꿔치기
                },
              });
            })
          ).flatMap(() => forward(operation)); // 3-3 방금 수정한 쿼리 재요청하기
        }
      }
    }
  });

여기 refreshToken을 받는 코드의 일부를 보면 마지막에 flatMap 으로 바꿔서 Observable 타입으로 변경해주었다.
왜인지 Observable의 특징을 아래에서 알아보자.

비동기 작업을 도와주는 도구로 Promise와 Observable이 있다.
Promise는 비동기 작업을 도와주고, Observable은 연속적인 비동기 작업을 도와준다.
차이는 연속적인 이다.

우리가 댓글이나 게시글을 올리거나 버튼을 누르는 행위를 할 때 빠르게 연타할 시, 다른 데이터를 받아오는 버튼을 빠르게 번갈아 누를 시, 검색어를 빠르게 바꿀 시 여러번 요청이 간다.
이 때 요청이 날아가면 순차적으로 처리되지 않아 처리된 순서대로 데이터를 브라우저에 보낸다는 문제점이 생긴다.
예시로 페이지네이션 1부터 10까지중에 3을 누르고 바로 5를 눌렀는데 3의 데이터 처리보다 5의 데이터처리가 더 빨리 끝나면 브라우저에는 5페이지가 뜨고 마지막에 3페이지가 뜬다.
사용자는 마지막에 누른 5페이지가 보고싶은데 3페이지가 뜬 것이다.
문제를 해결하려면 뒤에 다른 요청이 오면 앞에 요청 즉 3을 삭제해야한다. Promise는 한 개 보내고 기다리고 하기 때문에 문제를 해결하기 위해선 Observable을 이용한다.

Observable은 연속적인 것에 반응한다고 해서 반응형 프로그래밍이라고도 한다.
프로그래밍은 함수형 프로그래밍과 반응형 프로그래밍이 존재한다.
함수형은 함수들을 가지고 쉽게 만드는 걸 함수형 프로그래밍이라고 한다.
반응형은 연속적인 실행에 반응한다. 대표적으로 observable이 있고 이를 쉽게 쓸 수 있게 도와주는 reactive X => rx.js, zen-observable 등의 라이브러리가 있다.
아폴로 클라이언트가 업데이트 되면서 반응형이 내장되었다.

Apollo-Client는 업데이트 하면서 observable 기반으로 변경했는데 이때문에 리턴 시 Promise 타입을 observable 타입으로 바꿔줄 도구가 fromPromise이다.(apollo-client에 내장되어있는 zen-observable이라는 라이브러리에서 제공하는 도구이다.)

이 Observable 객체 안에 들어있는 요청들을 각각 forward(operation)을 이용하여 재요청 해주어야 한다. 이 작업을 위해서는 zen-observable에서 제공하는 flatMap이라는 기능을 사용했다.

이용하기에 앞서 apollo-client에서 지원하는 observable을 사용해야하기 때문에 아래 목록을 설치를 해준다.

🚦 설치목록
yarn add zen-observable

yarn add @types/zen-observable --dev

yarn add graphql-request

사용

import { Observable } from "@apollo/client";
import { from } from "zen-observable";

export default function ObservableFlatmapPage() {
  const onClickButton = () => {
    from(["1번 useQuery", "2번 useQuery", "3번 useQuery"]) // fromPromise
      .flatMap((el: string) => from([`${el} 결과에 qqq 적용`, `${el} 결과에 zzz 적용`]))
      .subscribe((el)=> console.log(el))
  };
  return <button onClick={onClickButton}>클릭</button>;
}

// 1번 useQuery 결과에 qqq 적용
// 1번 useQuery 결과에 zzz 적용
// 2번 useQuery 결과에 qqq 적용
// 2번 useQuery 결과에 zzz 적용
// 3번 useQuery 결과에 qqq 적용
// 3번 useQuery 결과에 zzz 적용
profile
총총

0개의 댓글