본문 바로가기
Web Development/Javascript

[Javascript / Java] Fetch API 응답 결과 값을 변수에 할당하는 방법 A to Z (feat. async/await, Promise)

by 감자맹고우 2022. 11. 17.
728x90
반응형

Spring Legacy 프로젝트를 주로 진행하기 때문에, ajax를 자주 사용하곤 한다.

그리고 최근 프로젝트가 다시 만들어지면서, jQuery를 최신 버전으로 업데이트하여 사용하게 되었다.

여기까진 문제가 없었지만, 언제나 버전 업데이트가 불쑥 문제를 일으키곤 한다.

 

일단 구현할 기능은 다음과 같다.

ⓐ 비밀번호를 입력 후 서버단으로 전송한다.
ⓑ 서버에서는 전송 받은 비밀번호가 DB의 비밀 번호와 일치하는지 확인한다.
ⓒ 확인 결과를 클라이언트단으로 넘겨준다.
ⓓ 클라이언트 단에서는 결과에 따라 이어서 진행한다.

 

지금 적으면서 생각해보니, ⓒ단계 진행없이 서버단에서 다 처리한 후 처리 결과를 클라이언트로 넘겨주면 끝이었는데, 기능별로 메소드를 구성하려는 욕심을 내다보니 프로세스가 더 추가된 것 같다.

 

아무튼 이를 구현하기 위해서는, Client→Server > Server→Client > Client→Server 로 통신이 이루어져야하는데, 단계적으로 이루어지므로 ajax 내에서 처리하든지, ajax에 async:false 기능을 이용해야한다.

그러나 jQuery가 업데이트되면서 async:false가 먹히지 않게 되었고, 코드가 콜백으로 복잡해지기 싫었던 나는 이 참에 fetch와 async/await을 공부할 겸 써보기로 했다.

역시 새로운 것은 쉽지 않았다.

 

일단, 편의상 주요 기능을 부분별로 나누어보겠다.

① fetch에서 json 형식의 객체 파라미터를 서버단에 전송하기
② fetch를 통해 전송한 data를 Controller에서 받기
③ 클라이언트단에서 서버단 결과값을 처리하기
④ fetch 응답 결과값을 javascript 변수에 할당하기
( ... 이후 과정은 생략 ... )

 

 

[ 적용 방법 ]

 

① fetch에서 json 형식의 객체 파라미터를 서버단에 전송하기

 

Fetch API의 기본 사용법은 MDN(https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch)에 잘 나와있다.

일단 기본적으로 Fetch는 URL[필수] 과 Option[선택] 이라는 1 ~ 2개의 매개변수를 가질 수 있다.

URL은 요청할 URL을 작성하면 되고, Option에는 method, mode, cache, headers, body 등의 옵션 목록을 선택적으로 작성하면 된다.

 

const input = '0000';

fetch('/test', {
    method: 'post',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        password: input
    })
})

 

즉, 위의 예시와 같이 작성하면 되는 것이다.

이 때, 데이터를 전송하려면 header에 content-type을 명시적으로 적어주어야 한다고 한다.

 

그리고, 위의 코드가 해결책을 포함하고 있다.

ajax에서는 data : {password : input} 과 같이 처리하던 부분을, fetch에서는 body에 {password : input}을 JSON.stringify로 감싸 JSON문자열로 변환하여 서버단에 전송할 수 있다.

 


② fetch를 통해 전송한 data를 Controller에서 받기

 

그럼 이제 Controller에서 파라미터를 받아보자.

구글링하면 여러가지 방법이 있다.

Gson 등을 사용해도 되지만, 그냥 json-simple로도 간단히 처리할 수 있다.

 

//pom.xml
<!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>

 

pom.xml에 흔하게 있을 json-simple을 사용하였다. 버전은 아무거나 써도 될 것 같다.

JSON 파싱만 해주면 되므로 다른 의존성을 주입했다면 그에 맞는 방식으로 처리하면 된다.

(maven repository를 보니 json-simple에 보안 취약점이 있으므로 어디까지나 참고용)

 

//Controller
@ResponseBody
@RequestMapping(value="/test", method=RequestMethod.POST)
public String test(HttpSession session, @RequestBody String data) {
    String samplePassword = "0000";

    JSONParser parser = new JSONParser();
    JSONObject obj = null;
    try {
        obj = (JSONObject) parser.parse(data);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    
    String password = (String) obj.get("password");
    
    //Boolean값은 @Responsebody 로 반환할 수 없는 듯 보인다.(Converter Error)
    //false 대신 null값을 넘겨주면 fetch에서 catch로 잡힌다.
    return password.equals(samplePassword) ? "true" : null;
}

 

위의 코드와 같이 JSONParser를 통해 파싱하고 키 값을 통해 데이터를 받아올 수 있다.

이를 클라이언트단으로 다시 넘겨줄 때는, 주석에서 적은 것 처럼 false 대신 null로 반환하면, Fetch API에서 then으로 넘어가지 않고 catch로 잡히기 때문에 편의상 null로 작성해주었다.

 


③ 클라이언트단에서 서버단 결과값을 처리하기

 

이 부분은 ①의 코드에 결과값 처리 부분이 추가된다.

 

const input = '0000';

fetch('/test', {
    method: 'post',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        password : input
    })
})
.then(response => response.json())
.then(data => {
    console.log(data);
})
.catch(error => {
    alert('비밀번호를 다시 입력해주세요.');
})

 

fetch의 요청 부분은 동일하다.

then부분은 서버의 응답을 json 형태로 파싱하고, 파싱된 데이터를 콘솔로 출력하도록 동작이 순차적으로 진행되게끔 하였다.

catch부분은 ②의 설명과 마찬가지로, null 값이 반환될 때 혹은 에러가 발생했을 때 동작하는 부분이다.

 

여기까지, 서버에서 응답된 data를 콘솔에 출력하는 것까지는 큰 문제가 없다.

그러나 data를 javascript에서 따로 생성한 변수에 할당하려면 문제가 생긴다.

문제는 ajax와 동일하게 비동기로 동작하기 때문에 코드의 순서가 보장되지 않아서 발생한다.

그렇기 때문에 undefined가 열심히 출력될 것인데, 이를 해결하는 방법이 있다!

 

반응형


④ fetch 응답 결과값을 javascript 변수에 할당하기

 

문제 해결방법으로, NodeJS를 공부하면서 알게되었던 async와 await가 떠올랐다.

그런데 여기서 실 사용을 해보려 하니 잘 되지 않았다.

최종 결과물은 data를 return 하고 async / await을 사용했을 때, Promise 자체가 반환되었던 것 같다...

 

다행히 더 열심히 구글링을 해본 결과, 해결법을 찾을 수 있었다.

 

const main = async () => {
    const input = '0000';
    
    let result = await test(input);
    
    //String to Boolean
    result = JSON.parse(result);
    
    //result가 true면 이어서 처리할 코드 작성
    if(result) {
        console.log(result);
    }
}

const test = (password) => {
    //Promise로 fetch를 감싼다
    return new Promise((resolve, reject) => {
        fetch('/test', {
            method: 'post',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({password}) //key, value 동일한 명칭일 때 value 생략 가능
        })
        .then(response => response.json())
        .then(data => {
            //Promise resolve 메소드를 이용해 success 시 data를 반환
            resolve(data);
        })
        .catch(error => {
            alert('비밀번호를 다시 입력해주세요.');
        })
    });
}

 

방법은 fetch에서 data를 바로 받아오지 않고,

Promise로 감싸서 resolve 메소드를 통해, success 일 때 data를 반환하도록 한다.

그리고 async, await을 통해 Promise가 이행된 후 다음 코드가 동작하게 한다.

(MDN : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve )

 

이렇게 하면 Promise가 이행된 후 data의 값이 Javascript 변수 result에 따로 할당되고, 이를 이용해 이후 작업이 단계적으로 동작하도록 처리할 수 있다!

 

 

--------------------------------------- 2023. 04. 27 추가 ---------------------------------------

 

Controller에서 @RequestBody 를 작성하지 않고 VO나 DTO 등으로 직접 받을 수 있다.

 

 

🤞 도움이 되셨기를 바랍니다. 한 번의 클릭과 댓글은 어딘가의 누군가에게 진실로 큰 힘이 됩니다. 🐱‍🏍

 

728x90
반응형

댓글