JS Proxy 사용기

Caesars·2024년 1월 22일
0

JS

목록 보기
8/8

인증 작업이 끝나지 않은 상황에서 고객사에서 api 호출을 해서 문제가 되는 상황이 있었습니다. 다행이 Proxy를 사용해 해결했고 써본김에 Proxy에 대해 정리해서 글을 작성합니다.


JS Proxy란

Proxy는 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체입니다. 가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달되기도 합니다.

Proxy는 다양한 라이브러리와 몇몇 브라우저 프레임워크에서 사용되고 있습니다. 이 글에서는 Proxy를 어떻게 실무에 적용할 수 있을지 다양한 예제를 통해 살펴보겠습니다
두 개의 매개변수를 사용하여 Proxy를 생성합니다.

  • target: 프록시할 원본 객체
  • handler: 가로채는 작업과 가로채는 작업을 재정의하는 방법을 정의하는 객체입니다.

Proxy 활용

프록시에서 사용 가능한 주요 트랩(trap) 몇 가지를 살펴보겠습니다.

  • get(trap): 속성 읽기 동작을 재정의합니다.
  • set(trap): 속성 쓰기 동작을 재정의합니다.
  • apply(trap): 함수 호출 동작을 재정의합니다.
  • has(trap): in 연산자를 재정의하여 속성의 존재 여부를 결정합니다.
  • deleteProperty(trap): delete 연산자를 재정의하여 속성을 삭제합니다.
const handler = {
  get: function(target, prop) {
    console.log(`Reading property ${prop}`);
    return target[prop];
  },
  set: function(target, prop, value) {
    console.log(`Writing property ${prop} with value ${value}`);
    target[prop] = value;
    return true;
  },
  apply: function(target, thisArg, argumentsList) {
    console.log(`Calling function with arguments ${argumentsList}`);
    return target.apply(thisArg, argumentsList);
  },
  has: function(target, prop) {
    console.log(`Checking if property ${prop} exists`);
    return prop in target;
  },
  deleteProperty: function(target, prop) {
    console.log(`Deleting property ${prop}`);
    delete target[prop];
    return true;
  }
};

const proxyObject = new Proxy(targetObject, handler);

// 사용 예시
console.log(proxyObject.name); // Reading property name, John
proxyObject.age = 30; // Writing property age with value 30
proxyObject.sayHello = function() { console.log("Hello!"); };
proxyObject.sayHello(); // Calling function with arguments
console.log('name' in proxyObject); // Checking if property name exists, true
delete proxyObject.age; // Deleting property age

이슈 해결

보통 서비스 기업에서는 자사 웹 서비스를 이용할 수 있는 스크립트 파일을 제공합니다. 그리고 고객사에서는 스크립트 SDK를 설치하고 사용하기 전에 초기화를 실행합니다. 해당 키가 정상인지 권한이 있는지 등등.. 아래는 카카오 SDK 사용 예시 입니다.

<script src="https://t1.kakaocdn.net/kakao_js_sdk/2.6.0/kakao.min.js"
  integrity="sha384-6MFdIr0zOira1CHQkedUqJVql0YtcZA1P0nbPrQYJXVJZUkTk/oX4U9GhUIs3/z8"></script>

<script>
const jsKey = "key";

if (!window.Kakao.isInitialized()) {
  // SDK 초기화
  window.Kakao.init(jsKey);
  // 로그인
  window.Kakao.Auth.login({...});
}
</script>

만약 위 상황에서 초기화 실행 전에 로그인이나 채팅창 기능을 사용하려고 하면 에러가 발생하게 됩니다. 저희도 에러를 반환하는 기능으로 끝냈으면 간단하겠지만.. 고객 편의를 위해 인증전에 기능 호출시 인증이 끝나고 난 후에 실행해주는 방안으로 진행했습니다.

코드변경

기존

function init(){
    let initRequest = new XMLHttpRequest();
    initRequest.open("POST", url);
    initRequest.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');

    initRequest.onload = function() {
        // 인증 성공 처리
    };
    initRequest.send(body);
}
...


// 외부 호출
service.action(action, obj);
service.getMessage(key).then((data)=> {});

변경

// 초기화 여부 변수
let initCallStateAction = new Proxy({value : 0}, { // 상태 0:대기, 1:실행중, 2:종료
  set(target, key, value) {  return Reflect.set(target, key, value); }
});
let initCallStateMessage = new Proxy({value : 0}, { // 상태 0:대기, 1:실행중, 2:종료
  set(target, key, value) {  return Reflect.set(target, key, value); }
});  

function init(){
    let initRequest = new XMLHttpRequest();
    initRequest.open("POST", authUrl);
    initRequest.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');

    initRequest.onload = function() {
        initCallStateAction.value = 2;  // 호출 완료시 종료로 변경
      	initCallStateMessage.value = 2;
    };

    initRequest.send(body);
    initCallStateAction.value = 1;	// 호출시 실행중 으로 변경
  	initCallStateMessage.value = 1;
}


// 
service.action = function(type, obj, callback) {
    
    if(initCallStateAction.value == 1){	// 실행중이라면 프록시 객
        initCallStateAction = new Proxy({value : 1}, {
            set(target, key, value) {
                actionAfterInitCall(type, obj, callback);
                return Reflect.set(target, key, value);
            }
        });
    }else{
        actionAfterInitCall(type, obj, callback);	// 종료되었다면 기존 흐름대로 작동 
    }
    
}


service.getMessage = function(messageKey){
    
    let result;

    if (initCallStateMessage.value == 1) {
        return new Promise((resolve, reject) => {
            initCallStateMessage = new Proxy({ value : 1 }, {
                set(target, key, value) {
                    getMessageAfterInitCall(messageKey)
                        .then(data => {
                            result = data;
                            resolve(result);
                        })
                        .catch(error => {
                            reject(error);
                        });
                    return Reflect.set(target, key, value);
                }
            });
        });
    } else {
        result = getMessageAfterInitCall(messageKey);
        return result;
    }
}

// 외부 호출
service.action(action, obj);
service.getMessage(key).then((data)=> {});

Proxy의 set 메소드를 활용했습니다. 특정 값이 변할 때 콜백 함수를 실행하거나 원하는 로직을 트리거할 수 있습니다.

기존 호출 방식은 그대로고 내부 구현만 바꿨습니다. 함수를 호출시 기존 초기화 작업이 끝나지 않을 경우에는 초기화 작업이 끝날 때까지 대기하고 그 후에 함수를 실행합니다.

추가 실전 사례


참고

https://brunch.co.kr/@skykamja24/644
https://velog.io/@esthevely/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-Proxy-%EA%B0%9D%EC%B2%B4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://ko.javascript.info/proxy
https://www.javascripttutorial.net/es6/javascript-proxy/
https://blog.logrocket.com/practical-use-cases-for-javascript-es6-proxies/

profile
잊기전에 저장

0개의 댓글