[angularJS] interceptor 활용하기

CUD Api 중복 호출 막기

develop, angularJS
written byzuhern1zuhern

in

2019. 04. 08


요구사항 발생 원인

더블 클릭으로 인한 POST 중복 호출로 동일 데이터가 여러개 생성

해결 방안 모색

버튼에서 더블 클릭을 방지하는 방안

방안 비고
기존 화면에 있는 기본 버튼을 custom 한 directive로 대체 테스트 시간 소요, 누락 가능성
버튼 외에 키보드 이벤트 등 으로도 api 가 호출 가능 모든 경우의 수 고려 필요, 누락 가능성
위 경우의 수 누락을 방지하기 위한 api 중복 호출을 막기 결국 중복 기능이 되어버림

api 호출 관련이 아닌 중복 동작 처리에 사용하는 것이 적합해 보임

api 호출 직전에 중복 호출 막는 방안

방안 비고
interceptor 를 이용하여 api 호출 직전, 이전 api를 체크 interceptor request
다른 directive 에서 동일한 api를 거의 동시에 호출하는 경우 문제 발생 POST, PUT, DELETE 만 대상
코드로 인한 연속 호출인 경우에 문제가 발생할 수 있음 필요시 예외 flag
화면에서 사람에 의한 중복 호출만 있다는 가정

인터페이스를 통한 막기 보다는, 최종 단계 직전에서 막는 것이 더 심플할 것으로 판단
interceptor 사용하는 것이 더 효과적이라고 생각 됌

interceptor

angularJS $http 에서 제공함.
현재 개발중인 프로젝트는 1.6.10 1.6.10 버전은 $resource interceptor 는 response, responseError 만 처리할 수 있음. (1.6.10 버전 $http는 request, requestError 제공)

프로젝트 angularJS 버전을 올려야 함 > 1.7.8 로 올림 > 모든 기능 테스트 필요

참조

migrating-from-1.6-to-1.7
$resource:1.6.10
$resource:latest
$http#interceptors:1.6.10
$http#interceptors:latest

api 중복 호출 처리

    
    /**
     * 더블 클릭 방지 코드
     * 목적: 더블 클릭 방지
     *      동일 url 이 response 되지 않은 상황에서 재호출 막기
     * 동일 url, 동일 method 을 호출할 경우 중복으로 처리
     * response, responseError 로 반드시 종료 처리가 되기 때문에, 요청 시간 정보는 불필요
     * url을 key 로 하면 탐색시간이 O(1) 이므로 검색 속도가 빠르므로 simple 하게 
     * {
     *   'GET': {
     *     url1: true,
     *     url2: true
     *   },
     *   'POST': {
     *     url1: true,
     *     url2: true
     *   }
     * }
     * 위 형태로 요청 중인 정보를 가지고, response(Error) 시 delete 처리
     * @author heather
     */
    var requestingUrlData = {};

    function addRequestData(config) {
      if (config.method === 'GET') { return; }
      requestingUrlData[config.method] = requestingUrlData[config.method] || {};
      requestingUrlData[config.method][config.url] = true;
    }

    function removeRequestData(config) {
      if (config.method === 'GET') { return; }
      var methodData = requestingUrlData[config.method] || {};
      delete methodData[config.url];
    }

    function isDoingSameRequest(config) {
      var methodData = requestingUrlData[config.method] || {};
      return methodData[config.url];
    }
    /** - 더블 클릭 방지 코드 끝 */

    return {
      request: function(config) {
        if (!config.passSameCall && isDoingSameRequest(config)) { 
          // '중복 허용이 아님' && '현재 동일 api 호출 후 리턴이 오지 않음'
          // passSameCall 는 resource 호출 시 설정 함.
          return $q.reject({ 
            error: 'same request called', 
            pass: true, 
            config: config 
          });
        }
        // 호출 시작
        addRequestData(config);
        return config;
      },
      requestError: function(rejection) {
        // 호출 완료
        removeRequestData(rejection.config);
        return $q.reject(rejection);
      },
      response: function (response) {
        // 호출 완료
        removeRequestData(response.config);
        return response.data;
      },
      responseError: function (error) {
        // 호출 완료
        removeRequestData(error.config);
        if (error.pass) {
          return $q.reject(error);
        } 

        return error.data;
      }
    };
// $resouce 호출
doApi : function() {

  var url = 'url';
  var action = actionFactory.defaultAction;
  var params = {};

  // 중복호출 허용 설정
  action.create.passSameCall = true;

  return $resource(url, params, action);
},