[JS] 비동기 자바스크립트 - Callback, Promise, Async/Await
비동기 작업은 동기 작업과 달리 순서가 보장되지 않는다. 하지만 비동기를 써야 하는데 순서대로 실행하는 것이 필요한 경우도 있을 수 있다. 예를 들어 네트워크에 요청을 보낸 후 받은 값으로 또 다른 요청을 한다고 하면 첫 번째 요청이 먼저 실행된 후에 두 번째 요청이 실행되어야 한다. 이런 상황을 자바스크립트에서는 콜백, 프로미스, async/await으로 다룰 수 있다.
cf 비동기 - 자바스크립트에서는 어떻게 비동기 작업을 처리할까?
Callback
- 콜백 함수란 다른 함수에 인자로 전달되어 그 함수의 내부에서 특정 시점에 호출되는 함수를 말한다.
- 자바스크립트 함수는 일급객체이기 때문에 함수를 인자로 받을 수 있다.
- 콜백은 방식에 따라 동기적/비동기적 콜백으로 나눌 수 있다.
function func1(func) {
console.log('1');
func();
}
function func2(func) {
setTimeout(function () {
console.log('2');
}, 0); // Asynchronous Callback
func();
}
function func3() {
console.log('3');
}
func1(func3);
// '1'
// '3'
// undefined
func2(func3);
// '3'
// undefined
// '2'
// setTimeout 안에 있는 콜백함수는 비동기적으로 처리된다.
// 따라서 func2의 실행이 다 끝나고 call stack이 빈 다음에야
// event loop에 의해 다시 call stack으로 돌아와 처리되기 때문에
// console창에 가장 나중에 찍힌 것이다.
Callback Hell
- 비동기 처리를 위한 콜백이 처리 순서를 보장하기 위해 중첩되면서 복잡도가 높아지는 현상. 가독성이 떨어지고 오류를 찾기 어려워진다.
- ES6부터 Promise가 도입되어 콜백을 중첩하는 대신
then
을 체이닝하여 순차적으로 비동기 처리가 가능해졌다. ES7에서는 여기에async
/await
이 추가되어 비동기 작업을 마치 동기인 것처럼 코드를 작성하여 Promise를 더욱 직관적으로 사용할 수 있게 되었다.
Promise
- 프로미스는 이후 시점 언젠가에 완료되거나 실패할 비동기 작업을 대리 표현하는 객체이다.
- 동기도 가능하지만 비동기에 특화되어 있다.
- 프로미스는 pending, fulfilled, rejected 세 가지 중 하나의 상태를 가진다.
- Promise 객체를
new
키워드로 인스턴스화 하여 사용한다. - 프로미스 실행 함수는 인자로
resolve
,reject
함수를 받는다.
new Promise((resolve ,reject) => {
if (err) {
reject(err)
} else {
resolve(result)
}
})
- 프로미스가 이행되면 이행된 값은 then의 콜백함수의 첫번째 인자로, 거부되면 거부된 이유를 then의 콜백함수의 두번째 인자 또는 catch의 콜백함수의 첫번째 인자로 받을 수 있다.
- 프로미스의 끝에 가급적
catch
를 붙여 발생할 수 있는 에러를 처리한다.
Await/Async
- ES7에서 도입
- Promise 위에서 syntactic sugar로 작용하며 비동기 작업을 마치 동기 작업인 것 처럼 작성할 수 있도록 해 준다.
- 비동기 작업을 핸들링하는 함수는 반드시
async
키워드로 표시해야 한다. async 함수는 항상 Promise를 반환한다. await
은async
표시 된 함수 안에서만 사용할 수 있다.await
키워드를 만나면 Promise가 resolve되거나 reject될 때까지 함수 실행이 멈춘다.await
은 주의해서 사용해야 하며 비동기 작업이 아주 많이 필요한 경우Promise.all()
이 더 적당하다.
Promise.all()
- 프로미스들을 담은 배열을 인자로 받는다. 프로미스를 반환하며 반환된 프로미스는 인자로 받은 배열의 모든 프로미스가 이행되었거나, 혹은 프로미스가 주어지지 않았을 때 이행된다. 주어진 프로미스들 중 하나라도 거부되면 거부된다.
- 반환된 프로미스의 이행 값은 배열 형태며 인자로 받은 배열의 프로미스들의 이행 값이 차례로 담겨 있다.
- 비동기 작업 A가 끝나고 그 값을 받아 B를 진행해야하는 경우 체이닝, A, B가 순서 상관없이 동시에 처리되는 경우
Promise.all()
을 사용한다. 반드시 그래야 하는 것은 아니고 상황에 따라 적절하게 사용한다.
AJAX
- AJAX (Asynchronous Javascript And XML)
- 정보 교환 기법의 하나로, 페이지 로드가 끝난 후 경우에 따라 필요한 특정 데이터만 가져와 변경하기 위해 비동기적으로 정보를 요청하기 위해 만들어졌다.
- 만들어질 당시와 달리 현재는 XML보다 JSON이 더 많이 사용되지만 원래 이름대로 AJAJ가 아닌 AJAX라고 불린다.
XMLHttpRequest (by Callback)
- ES6 이전의 오래된 AJAX 구현 방법
- Promise를 지원하지 않기 때문에 콜백만 사용할 수 있다.
Fetch (by Promise)
- ES6부터 도입된 방법으로 AJAX를 쉽게 하용하게 해주는 방법
- fetch는 프로미스를 반환하며 이 프로미스가 resolve된 다음 연결된 then에서 resolve된 값을 다룰 수 있다. (reject되면 catch에서 reject된 이유를 다룰 수 있다.)
Async/Await (by Promise)
- ES7에서 도입
- Promise 위에서 syntactic sugar로 작용하며 비동기 작업을 마치 동기 작업인 것 처럼 작성할 수 있도록 해 준다.
- 비동기 작업을 핸들링하는 함수는 반드시
async
키워드로 표시해야 한다. async 함수는 항상 Promise를 반환한다. await
은async
표시 된 함수 안에서만 사용할 수 있다.await
키워드를 만나면 프로미스가 resolve되거나 reject될 때까지 함수 실행이 멈춘다.
AJAX 구현 방법 비교
구현하고자 하는 것 :
- swapi에 등장인물에 대한 정보를 랜덤하게 요청
- 받은 등장인물의 이름을 콘솔창에 출력
- swapi에 받은 등장인물이 등장하는 영화들 중 하나에 대한 정보를 요청
- 받은 영화의 제목을 콘솔창에 출력
첫 번째 요청이 완료되어야 두 번째 요청이 발생할 수 있다.
XMLHttpRequest로 구현
// 랜덤한 등장인물에 대한 정보를 요청할 URL 구하기
const randNum = Math.floor(Math.random() * 82) + 1;
const peopleURL = `https://swapi.dev/api/people/${randNum}`;
// 랜덤한 등장인물에 대한 정보 요청
const peopleReq = new XMLHttpRequest();
peopleReq.addEventListener("load", function () {
console.log("First request loaded");
const data = JSON.parse(this.responseText);
// 받은 등장인물의 이름 콘솔창에 출력
console.log("Character Name : " + data.name);
const filmURL = data.films[0];
// 받은 등장인물이 등장하는 영화들 중 하나에 대한 정보 요청
const filmReq = new XMLHttpRequest();
filmReq.addEventListener("load", function () {
console.log("Second request loaded");
const data = JSON.parse(this.responseText);
// 받은 영화의 제목 콘솔창에 출력
console.log("Appearing In : " + data.title);
});
// 영화 정보 요청 오류 처리
filmReq.onerror = function (err) {
console.log("Error : ", err);
};
// 영화 정보 요청 보내기
filmReq.open("GET", filmURL);
filmReq.send();
});
// 등장인물 정보 요청 오류 처리
peopleReq.onerror = function (err) {
console.log("Error : ", err);
};
// 등장인물 정보 요청 보내기
peopleReq.open("GET", peopleURL);
peopleReq.send();
Fetch로 구현
// 랜덤한 등장인물에 대한 정보를 요청할 URL 구하기
const randNum = Math.floor(Math.random() * 82) + 1;
const peopleURL = `https://swapi.dev/api/people/${randNum}`;
// 랜덤한 등장인물에 대한 정보 요청
fetch(peopleURL)
.then((response) => response.json())
.then((data) => {
console.log("First request loaded");
// 받은 등장인물의 이름 콘솔창에 출력
console.log("Character Name : " + data.name);
const filmURL = data.films[0];
// 받은 등장인물이 등장하는 영화들 중 하나에 대한 정보 요청
return fetch(filmURL)
.then((response) => response.json())
.then((data) => {
console.log("Second request loaded");
// 받은 영화의 제목 콘솔창에 출력
console.log("Appearing In : " + data.title);
});
})
// 요청 오류 처리
.catch((err) => {
console.log("Error : ", err);
});
Async/Await으로 구현
// 비동기 함수 getCharacterAndFilm 선언
async function getCharacterAndFilm() {
// 랜덤한 등장인물에 대한 정보를 요청할 URL 구하기
const randNum = Math.floor(Math.random() * 82) + 1;
const peopleURL = `https://swapi.dev/api/people/${randNum}`;
// 랜덤한 등장인물에 대한 정보 요청
const filmURL = await fetch(peopleURL)
.then((response) => response.json())
.then((data) => {
console.log("First request loaded");
// 받은 등장인물의 이름 콘솔창에 출력
console.log("Character Name : " + data.name);
return data.films[0];
})
// 요청 오류 처리
.catch((err) => {
console.log("Error : ", err);
});
// 받은 등장인물이 등장하는 영화들 중 하나에 대한 정보 요청
fetch(filmURL)
.then((response) => response.json())
.then((data) => {
console.log("Second request loaded");
// 받은 영화의 제목 콘솔창에 출력
console.log("Appearing In : " + data.title);
})
// 요청 오류 처리
.catch((err) => {
console.log("Error : ", err);
});
}
// getCharacterAndFilm 호출
getCharacterAndFilm();
Author And Source
이 문제에 관하여([JS] 비동기 자바스크립트 - Callback, Promise, Async/Await), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@alexjlee/JS-비동기-자바스크립트-Callback-Promise-AsyncAwait저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)