[꼬꼬공] 콜백 함수, Promise, Fetch API, async/await
비동기는 작업 순서가 정확히 보장되지 않는다.
따라서 만약 비동기 처리로 얻은 데이터를 이용해야 하는 특정 작업이 있다면, 비동기 처리가 모두 완료된 후에 특정 작업을 수행할 수 있도록 제어해야 한다.
이를 위한 방법으로 콜백(call back) 함수가 있다.
🔗 콜백 함수
콜백(call back) 함수 : 어떤 함수의 인수로 전달되는 함수
function order(food, callback) {
console.log(`${food} 도착`);
callback();
}
function eat() {
console.log('먹는다');
}
order('짜장면', eat);
위에 주어진 코드를 보면 order 함수는 두 개의 인자를 받는다.
첫 번째 인자는 음식 이름(food), 두 번째 인자는 콜백 함수(callback)다.
이 order 함수는 음식이 도착했다는 메시지를 출력한 후에 콜백 함수를 실행한다.
eat 함수는 콜백 함수로 사용되며, 이 함수는 "먹는다"라는 메시지를 출력한다.
따라서 order('짜장면', eat) 를 실행하면 먼저 "짜장면 도착"이 출력되고, 이어서 "먹는다"가 출력된다.
이렇게 eat 함수는 order 함수에 인자로 전달되어 order 함수의 내부에서 실행되는 콜백 함수가 된다.
그런데 콜백 함수를 과도하게 중첩하여 사용하면 코드의 복잡성을 높혀 가독성이 떨어지고 디버깅하기가 어렵다.
이를 흔히 콜백 지옥이라 부른다.
이러한 콜백 지옥을 피하는 방법으로 promise가 있다.
🔗 Promise
Promise : 비동기 처리 수행 여부에 따른 상태 값과 결과 값을 가진 객체
promiseState : 현재 수행 상태
promiseResult : 결과
비동기 요청을 아직 수행하지 않은 상태라면 promiseState는 pending, 비동기 요청이 완료됐다면 fullfilled가 된다.
promise는 다음과 같이 비동기 함수를 넣어주며 연산이 성공하면 resolve, 실패하면 reject를 호출한다.
const executor = (resolve, reject) => {
const xhr = new XMLHttpRequest();
const url = "https://jsonplaceholder.typicode.com/posts/1";
xhr.open("GET", url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 응답 성공
const result = JSON.parse(xhr.response);
resolve(result);
} else {
// 에러 처리
reject(xhr.status);
}
};
};
const myPromise = new Promise(executor);
myPromise
.then((response) => console.log(`응답 결과:`, response)) // resolve가 호출되면 then()이 호출됨
.catch((error) => console.error(`error: ${error}`)); // reject가 호출되면 catch()가 호출됨
promise는 연산이 성공하면 실행되는 then, 실패하면 실행되는 catch, 성공 여부와 관계없이 항상 실행되는 finally와 같은 메서드를 제공한다.
이러한 promise를 활용해 네트워크 요청을 처리하는 것이 Fetch API이다.
🔗 Fetch API
Fetch : 가지고오다, 불러오다
Fetch API : 웹에서 데이터를 불러오는 API
XMLHttpRequest보다 간소화된 API로, JavaScript에서 비동기 HTTP 요청을 처리하는 가장 간단한 방법 중 하나이다.
기본 코드는 다음과 같다.
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
1. 주어진 url로 http 요청을 보내고
2. 서버로부터 응답을 받으면 JSON 형식으로 변환하고
3. 그 결과를 콘솔에 출력한다.
options에는 HTTP 방식(method), HTTP 요청 헤더(headers), HTTP 요청 전문(body) 등을 설정해줄 수 있다.
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
key1: 'value1',
key2: 'value2'
})
})
.then(response => response.json())
.then(data => console.log(data))
이러한 fetch는 Promise를 반환한다.
fetch가 promise를 반환하기 때문에 promise가 제공하는 then을 바로 붙여 사용할 수 있고 그 then도 새로운 promise를 반환하기 때문에 또 then을 이어서 사용할 수 있는 것이다.
fetch('https://api.example.com/data1')
.then(response => response.json())
.then(data1 => {
console.log(data1);
return fetch('https://api.example.com/data2');
})
.then(response => response.json())
.then(data2 => {
console.log(data2);
})
.catch(error => console.error('Error:', error));
이렇게 then을 이어서 계속 사용하는 방식을 promise chaining이라 한다.
그런데 이러한 promise기반의 비동기를 동기처럼 작동하도록 돕는 async/await가 있다.
🔗 asnyc / await
asnyc와 await는 promise기반의 비동기 코드를 마치 동기 코드처럼 작동하도록 돕는다.
async : async는 함수 앞에 위치하며, 이 키워드가 붙은 함수는 항상 promise를 반환한다.
만약 반환값이 promise가 아니라면, JavaScript는 이 값을 자동으로 promise로 감싸서 반환한다.
async function fetchUser() {
return 'John Doe';
}
fetchUser().then(alert); // 'John Doe'
await : await는 async 함수 내부에서만 사용할 수 있으며, promise의 결과를 기다린다.
await 키워드가 붙은 표현식은 promise가 처리될 때까지 함수의 실행을 멈추고, promise의 결과를 반환한다.
async function fetchUser() {
let response = await fetch('https://api.example.com/user');
let user = await response.json();
return user;
}
fetchUser().then(alert);
위의 예시에서 fetchUser 함수는 URL로부터 데이터를 가져오는 비동기 작업을 수행한다.
await 키워드 때문에 이 비동기 작업이 마치 동기 작업처럼 보인다.
fetch 함수의 결과를 기다린 후에 response.json()을 호출하고 그 결과를 기다린 후에 함수를 종료한다.