본문 바로가기
프로그래밍/Javascript

[Javascript] 비동기 처리: Promise객체, fetch, async / await

by 하나둘지금 2024. 4. 9.

지금까지 ajax로만 비동기처리를 했었는데, 찾아보니 Promise라는 객체를 이용해서 비동기 처리를 하는 경우가 많았다. fetch에 대해 정리해보고자 한다.

 

fetch

fetch는 Promise 기반의 API로,  JavaScript에서 웹 리소스를 비동기적으로 가져오기 위한 방법을 제공한다. 

fetch는 리소스의 경로(URL)를 최소 인자로 받는데,

fetch가 호출된 리소스에 대한 Promise를 반환하며, 반환된 Promise 객체는 요청이 성공적으로 완료되었을 때 HTTP 응답을 나타내는 Response 객체로 이행(resolved)된다.

fetch('https://example.com/data')
  .then(response => response.json()) // 응답을 JSON으로 파싱
  .then(data => console.log(data)) // 데이터 처리
  .catch(error => console.error('Error:', error)); // 오류 처리

 


fetch는 두 번째 인자로 옵션 객체를 받는다. 요청 메소드, 헤더, 본문 등을 설정할 수 있다.

fetch('https://example.com/data', {
  method: 'POST', // 요청 메소드
  headers: {
    'Content-Type': 'application/json', // 콘텐츠 타입 설정
  },
  body: JSON.stringify({
    name: 'John Doe',
    login: 'john.doe',
  }), // 보낼 데이터
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch(error => console.error('Error:', error));

 

 

async / await

promise객체와 fetch를 검색하다보면 async와 await도 많이 나오더라.

async와 await는 자바스크립트에서 비동기 작업을 더 쉽게 작성할 수 있게 해주는 문법이다. 
async 함수는 항상 Promise객체를 반환하며, 함수 내부에서 await 키워드를 사용하여 프로미스의 결과를 기다린다.

1. async 함수 선언: 함수 앞에 async 키워드를 추가한다. 이 함수는 프로미스를 반환한다.
2. await 표현식 사용: async 함수 내에서 프로미스의 결과가 필요할 때, await 키워드를 사용하여 프로미스의 해결을 기다린다. await는 async 함수 내에서만 사용할 수 있다.

 

async와 await를 사용하여 응답을 기다린 후, JSON으로 변환하고 콘솔에 결과를 출력한다.

// async 함수 선언
async function fetchData(url) {
  try {
    // await를 사용하여 프로미스의 해결을 기다림
    const response = await fetch(url);
    
    // 응답 상태 확인
    if (!response.ok) {
      throw new Error(HTTP error! status: ${response.status});
    }
    
    // 응답 본문을 JSON으로 변환
    const data = await response.json();
    
    // 데이터 처리
    console.log(data);
  } catch (error) {
    // 오류 처리
    console.error("Could not fetch data: ", error);
  }
}

// 함수 호출
fetchData('https://example.com/data');


만약 요청이 실패하거나 예외가 발생하면, catch 블록에서 이를 적절히 처리한다.


Promise?

Promise의 가장 큰 장점은 비동기 코드의 중첩(nesting)을 피할 수 있다는 점이다. 콜백 함수를 연속해서 사용하던 기존의 접근 방식(일명 콜백지옥이라고도 한다.)에서 벗어나 코드의 가독성을 높이고 유지 보수를 용이하게 한다.

비동기 작업이 처리되는 동안 Promise는 비동기 작업을 대기(pending), 이행(fulfilled), 거부(rejected)의 세 가지 상태로 관리한다.

 

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

 

어떻게 적용할까?

사용자 정보를 가져와서, 그 정보를 사용해 로그인, 로그인 성공 후 사용자의 프로필 정보를 가져오는 경우를 예시로 콜백함수를 promise 체이닝으로, 그리고 await으로 표현해봤다.

 

콜백지옥

getUser(userId, function(err, user) {
    if (err) {
        console.error('Error fetching user:', err);
    } else {
        loginUser(user, function(err, session) {
            if (err) {
                console.error('Login failed:', err);
            } else {
                getUserProfile(session, function(err, profile) {
                    if (err) {
                        console.error('Error fetching profile:', err);
                    } else {
                        console.log('Profile Data:', profile);
                    }
                });
            }
        });
    }
});

 

 

Promise 체이닝

// getUser, loginUser, getUserProfile이 Promise를 반환하도록 가정

function getUser(userId) {
    return new Promise((resolve, reject) => {
        // 비동기 작업 예시
        fetch(`https://api.example.com/users/${userId}`)
            .then(response => response.json())
            .then(data => resolve(data))
            .catch(err => reject(err));
    });
}

function loginUser(user) {
    return new Promise((resolve, reject) => {
        // 사용자 로그인 처리 예시
        fetch('https://api.example.com/login', {
            method: 'POST',
            body: JSON.stringify(user),
            headers: {
                'Content-Type': 'application/json'
            }
        })
        .then(response => response.json())
        .then(session => resolve(session))
        .catch(err => reject(err));
    });
}

function getUserProfile(session) {
    return new Promise((resolve, reject) => {
        // 프로필 정보 가져오기 예시
        fetch(`https://api.example.com/profiles/${session.userId}`)
            .then(response => response.json())
            .then(profile => resolve(profile))
            .catch(err => reject(err));
    });
}

// 함수 사용
getUser('userId')
    .then(user => loginUser(user))
    .then(session => getUserProfile(session))
    .then(profile => console.log('Profile Data:', profile))
    .catch(error => console.error('Error:', error));

 

 

async, await

// getUser, loginUser, getUserProfile이 각각 Promise를 반환한다고 가정

async function getUser(userId) {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    if (!response.ok) {
        throw new Error('Failed to fetch user');
    }
    return await response.json();
}

async function loginUser(user) {
    const response = await fetch('https://api.example.com/login', {
        method: 'POST',
        body: JSON.stringify(user),
        headers: { 'Content-Type': 'application/json' }
    });
    if (!response.ok) {
        throw new Error('Login failed');
    }
    return await response.json();
}

async function getUserProfile(session) {
    const response = await fetch(`https://api.example.com/profiles/${session.userId}`);
    if (!response.ok) {
        throw new Error('Failed to fetch profile');
    }
    return await response.json();
}

async function displayUserProfile(userId) {
    try {
        const user = await getUser(userId);
        const session = await loginUser(user);
        const profile = await getUserProfile(session);
        console.log('Profile Data:', profile);
    } catch (error) {
        console.error('Error:', error);
    }
}

// 함수 실행
displayUserProfile('userId');