자바스크립트 비동기 처리 방식이 자주 쓰입니다.
서버에게 리소스를 요청하거나 파일 입출력 혹은 타이머 대기 작업을 실행하는 Web APIs 들은 비동기 처리 방식으로 백그라운드로 동작되기 때문에 그 결과가 언제 반환될지 알수 없습니다.
그래서 완료되면 결과를 받아 처리하기 위해 사용되는 대표적인 방법으로 콜백 함수(Callback) 와 이를 개선한 프로미스 객체(Promise)가 있습니다.
하지만 서비스 규모가 커질 수록 코드가 복잡해짐에 따라 코드를 중첩해서 사용하다가 가독성이 떨어지고 유지보수가 어려워지는 상황이 발생되게 되는데, 이를 콜백지옥(Callback Hell), 프로미스 지옥(Promise Hell)이라고 합니다.
|
// 콜백 지옥(Callback hell)에 빠진다.
first(function () {
second(function () {
third(function () {
fourth(function () {
fifth(function () {
sixth(function () {
setTimeout(() => {
console.log("작업 종료!!!");
}, 1000);
});
});
});
});
});
});
// 프로미스 지옥(Promise hell)
step1(4)
.then((data)=>{
console.log(`data = ${data}`);
return step2(data);
})
.then((data)=>{
console.log(`data = ${data}`);
return step3(data);
})
.then((data)=>{
console.log(`data = ${data}`);
return step4(data);
})
.then((data)=>{
console.log(`data = ${data}`);
return step5(data);
})
.then((data)=>{
console.log(`data = ${data}`);
});
|
async 와 await는 위와 같은 콜백지옥(Callback Hell), 프로미스 지옥(Promise Hell)을 해결하기 위해 탄생 했습니다.
잘 사용하면 훨씬 간단하면서 가독성이 좋은 코드로 변신합니다.
|
// async와 await 사용
async function process(){
let data = await step1(6);
data = await step2(data);
data = await step3(data);
data = await step4(data);
data = await step5(data);
console.log(`결과값 : ${data}`);
}
process();
|
프로미스 지옥(Promise Hell)을 async 와 await를 이용하여 처리한 예제입니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
// step1 : a + 100
function step1(a) {
console.log(`data = ${a}`);
return new Promise((resolve, reject) => {
setTimeout(() => {resolve(a + 100)}, 1000);
});
}
// step2 : a * 2
function step2(a) {
console.log(`data = ${a}`);
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(a * 2)}, 1000);
});
}
// step3 : a / 3
function step3(a) {
console.log(`data = ${a}`);
return new Promise((resolve, reject) => {
setTimeout(() => {resolve(parseInt(a / 3))}, 1000);
});
}
// step4 : a - 50
function step4(a) {
console.log(`data = ${a}`);
return new Promise((resolve, reject) => {
setTimeout(() => {resolve(a - 50)}, 1000)
});
}
// step5 : a ** 2
function step5(a) {
console.log(`data = ${a}`);
return new Promise((resolve, reject) => {
setTimeout(() => {resolve(a ** 2)}, 1000)
});
}
// 프로미스 지옥(Promise hell)
step1(4)
.then((data)=>{
return step2(data);
})
.then((data)=>{
return step3(data);
})
.then((data)=>{
return step4(data);
})
.then((data)=>{
return step5(data);
})
.then((data)=>{
console.log(`결과값 : ${data}`);
});
// async와 await 사용
async function process(){
let data = await step1(6);
data = await step2(data);
data = await step3(data);
data = await step4(data);
data = await step5(data);
console.log(`결과값 : ${data}`);
}
process();
</script>
</head>
<body>
</body>
</html>
|

async/await 는 ES2017에 새롭게 도입된 문법으로 Promise 로직을 더 쉽고 간결하게 처리할 수 있도록 도와주는 역할을 수행합니다.
함수에 async 키워드를 선언하면 해당 함수를 AsyncFunction 객체를 반환하는 하나의 비동기 함수로 정의합니다.
비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환합니다.
즉, 함수에 async 키워드를 선언하면 해당 함수의 반환값이 자동으로 Promise가 됩니다.
또한, async 함수의 결과값(return)은 Promise의 resolve(값)으로 호출 됩니다.
async 공식 문서
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function
async function - JavaScript | MDN
async function 선언은 AsyncFunction객체를 반환하는 하나의 비동기 함수를 정의합니다. 비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환
developer.mozilla.org
await 키워드는 async function 내부에서만 사용이 가능하며 Promise를 기다리기 위해 사용됩니다.
즉, await문은 Promise가 이행(resolve)되거나 거부(reject)될 때까지 async 함수의 실행을 일시 정지하고, Promise가 이행(resolve)되면 async 함수를 일시 정지한 부분부터 실행합니다.
이때, await 문의 반환값은 Promise에서 이행(resolve)된 값이 됩니다. 만약, Promise가 거부(reject)되면, await문은 reject된 값을 throw 합니다.
await 키워드는 비동기 함수내에서 사용하며, 해당 비동기 함수내에서 다른 비동기 함수를 실행할 때 사용합니다.
await 공식 문서
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/await
await - JavaScript | MDN
Promise 혹은 기다릴 어떤 값입니다.
developer.mozilla.org
async 가 붙은 함수는 비동기 처리를 하는 작업들 앞에 await를 붙여 동기 방식처럼 차례대로 처리가 가능하도록 해주는 비동기함수들의 동기처리 실행기라고 보시면 편리 하겠습니다.
다음과 같이 다양한 방법으로 만들어 사용할 수 있습니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
// 비동기 함수의 작성
async function helloAsync() {
return "hello Async";
}
console.log(helloAsync());
// Promise 실행
helloAsync()
.then((res) => {
console.log(res);
});
// async&await를 이용하여 비동기 함수 실행기를 만들어 실행
async function asyncHello1(){
let res = await helloAsync();
console.log(res);
}
asyncHello1();
// 비동기 함수를 화살표 함수 문법으로 작성 가능합니다.
const asyncHello2 = async ()=>{
let res = await helloAsync();
console.log(res);
}
asyncHello2();
// 화살표 함수로 비동기 즉시 실행 함수를 만듬과 동시에 실행!!!
(async ()=>{
let res = await helloAsync();
console.log(res);
})();
// 이렇게는 사용못합니다.
// await는 async 함수 내에서 다른 비동기 함수 앞에 만 붙일 수 있습니다.
// console.log(await helloAsync());
</script>
</head>
<body>
</body>
</html>
|

async 와 await 는 절차적 언어에서 작성하는 코드와 같이 사용법도 간단하고 이해하기도 쉽습니다.
function 키워드 앞에 async 만 붙여주면 되고, 비동기로 처리되는 부분 앞에 await 만 붙여주면 됩니다.
"함수명 앞에 async를 붙이고 비동기 함수 호출 앞에 await를 붙인다."
다음은 프로미스 객체 비동기 함수를 기존 Promise.then() 방식과 async/await 방식으로 똑같이 처리하지만 다르게 코드를 구현한 예제입니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
// 데이터 분석 6단계
// 문제 제기 단계
function ask(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("문제 제기(Ask) >> ");
}, 1000);
});
}
// 준비 단계
function prepare(data){
return new Promise((resolve, reject) => {
console.log(data);
setTimeout(() => {
resolve(data + "준비(Prepare) >> ");
}, 1000);
});
}
// 처리 단계
function process(data){
return new Promise((resolve, reject) => {
console.log(data);
setTimeout(() => {
resolve(data + "처리(Process) >> ");
}, 1000);
});
}
// 분석 단계
function analyze(data){
return new Promise((resolve, reject) => {
console.log(data);
setTimeout(() => {
resolve(data + "분석(Analyze) >> ");
}, 1000);
});
}
// 공유 단계
function share(data){
return new Promise((resolve, reject) => {
console.log(data);
setTimeout(() => {
resolve(data + "공유(Share) >> ");
}, 1000);
});
}
// 실행 단계
function act(data){
return new Promise((resolve, reject) => {
console.log(data);
setTimeout(() => {
resolve(data + "실행(Act)");
}, 1000);
});
}
// Promise를 이용한 실행
ask()
.then((data)=>{
return prepare(data)
})
.then((data)=>{
return process(data)
})
.then((data)=>{
return analyze(data)
})
.then((data)=>{
return share(data)
})
.then((data)=>{
return act(data)
})
.then((data)=>{
console.log(`데이터 분석 6단계 : ${data}`);
});
// async/await를 이용한 실행
const launcher = async () =>{
data = await ask();
data = await prepare(data);
data = await process(data);
data = await analyze(data);
data = await share(data);
data = await act(data);
console.log(`데이터 분석 6단계 : ${data}`);
}
launcher();
// async/await를 이용한 즉시 실행
(async () =>{
data = await ask();
data = await prepare(data);
data = await process(data);
data = await analyze(data);
data = await share(data);
data = await act(data);
console.log(`데이터 분석 6단계 : ${data}`);
})();
</script>
</head>
<body>
</body>
</html>
|

다음은 식당에서 음식을 주문한다는 가정하에 구현해 보았습니다.
메뉴를 선택하는데 2초가 걸리고 결제 하는데 1초가 걸린다고 가정합니다.
잘못하면 결제가 먼저 되고 메뉴를 나중에 고르는 상태가 되어 버립니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
// 1. 메뉴를 선택한다. (2초)
function selectionMenu() {
return new Promise((resolve, reject) => {
setTimeout(() => {
//console.log("메뉴 선택 >> ");
resolve("메뉴 선택 >> ");
}, 2000);
});
}
// 2. 메뉴를 결제한다. (1초)
function payment(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
//console.log("결제 >> ");
resolve(data + "결제 >> ");
}, 1000);
});
}
function order(){
let data = selectionMenu();
data = payment(data);
console.log(data + "주문 완료!!!")
}
order();
selectionMenu()
.then((data)=>{
return payment(data);
}).then((data)=>{
console.log(data + "주문 완료!!!")
});
async function orderAwait() {
let data = await selectionMenu();
data = await payment(data);
console.log(data + "주문 완료!!!")
}
orderAwait();
</script>
</head>
<body>
</body>
</html>
|

원격 서버의 데이터를 요청해서 받아 보겠습니다.
다음의 사이트는 사용자 정보를 무작위로 JSON/CSV/YAML/XML 형태로 만들어 주는 사이트 입니다.
Random User Generator | Home
Copyright Notice All randomly generated photos were hand picked from the authorized section of UI Faces. Please visit UI Faces FAQ for more information regarding how you can use these faces. in North America. -->
randomuser.me
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
// 서버에서 받은 데이터를 html list로 만들어 주는 함수
const process = (data) => {
let result = "<ul>";
data.forEach(user => {
result += `<li>${user.name.title} ${user.name.first} ${user.name.last}(${user.email})</li>`;
});
result += "<ul><br/>";
return result;
}
// 서버에 사용자 정보 요청 : fetch 사용
.then((response) => {
// console.log(`1차로 받은 값 : ${typeof (response)} : ${response}`)
// console.log(JSON.stringify(response));
return response.json(); // json 객체로 변환
})
.then((json) => {
// console.log(`2차로 받은 값 : ${typeof (json)} : ${json}`);
// console.log(JSON.stringify(json));
document.querySelector('body').innerHTML += process(json.results);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log('1. 작업이 완료되었습니다.');
});
// 서버에 사용자 정보 요청 : async/await 사용
async function getResult() {
// console.log(`1차로 받은 값 : ${typeof (response)} : ${response}`)
const json = await response.json();
// console.log(`2차로 받은 값 : ${typeof (json)} : ${json}`);
return json.results;
}
getResult()
.then((data) => {
document.querySelector('body').innerHTML += process(data);
}).finally(() => {
console.log('2. 작업이 완료되었습니다.');
});
</script>
</head>
<body>
</body>
</html>
|

다음의 사이트는 무료로 가짜 API를 사용할 수 있어 각종 테스트를 진행할 수 있습니다.
JSON타입의 REST API를 연계해서 테스트를 진행해야 할 경우에 REST API 개발이 완료되지 않았을떄 유용하게 사용합니다.
https://jsonplaceholder.typicode.com
JSONPlaceholder - Free Fake REST API
{JSON} Placeholder Free fake and reliable API for testing and prototyping. Powered by JSON Server + LowDB. Serving ~3 billion requests each month.
jsonplaceholder.typicode.com
직접 사용자가 JSON-SERVEER를 이용하여 임시 서버를 만들어 사용해보셔도 됩니다.
https://www.npmjs.com/package/json-server
json-server
[](https://github.com/typicode/json-server/actions/workflows/node.js.yml). Latest version: 1.0.0-beta.3, last published: a month ago. Start using json-server in y
www.npmjs.com
json-server 사용방법은 다음의 주소에 있습니다.
https://101developer.tistory.com/98
[JavaScript] JSON-SERVER 사용해 보기
json-server란?JSON Server는 JSON 파일을 이용해 모의용 RESTful API 서버 및 DB를 구축할 수 있는 도구입니다.백엔드 API 서버나 DB가 준비되지 않은 상황에서도 프로토타입을 개발하거나 테스트
101developer.tistory.com
]
fetch 함수를 간단하게 사용할 수 있도록 사용자 함수를 만들어 CRUD(저장/읽기/수정/삭제) 작업을 해보도록 하겠습니다.
|
async function myFetch(url = "", method = "GET", data = {}) {
let option = {
method: method, // *GET, POST, PUT, DELETE 등
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
// manual, *follow, error
redirect: "follow",
// no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin,
// same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url referrerPolicy: "no-referrer",
};
// GET 방식이 아닐 경우에만 보내는 데이터가 있다.
if(method!=='GET'){
option.body = JSON.stringify(data); // body의 데이터 유형은 반드시 "Content-Type" 헤더와 일치해야 함
}
const response = await fetch(url, option);
const json = await response.json();
return json; // JSON 응답을 네이티브 JavaScript 객체로 파싱
}
|
전체 소스 입니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async & await</title>
<script>
async function myFetch(url = "", method = "GET", data = {}) {
let option = {
method: method, // *GET, POST, PUT, DELETE 등
mode: "cors", // no-cors, *cors, same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin", // include, *same-origin, omit
headers: {
"Content-Type": "application/json",
// 'Content-Type': 'application/x-www-form-urlencoded',
},
// manual, *follow, error
redirect: "follow",
// no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin,
// strict-origin, strict-origin-when-cross-origin, unsafe-url referrerPolicy: "no-referrer",
};
// GET 방식이 아닐 경우에만 보내는 데이터가 있다.
if(method!=='GET'){
option.body = JSON.stringify(data); // body의 데이터 유형은 반드시 "Content-Type" 헤더와 일치해야 함
}
const response = await fetch(url, option);
const json = await response.json();
return json; // JSON 응답을 네이티브 JavaScript 객체로 파싱
}
// GET : 읽기
.then((data) => {
console.log(JSON.stringify(data));
document.querySelector('body').innerHTML += JSON.stringify(data);
})
.finally(() => {
console.log('데이터 읽기를 완료했습니다.');
});
// POST : 저장
userId: 1,
title: "Test",
body: "I am testing!",
}).then((data) => {
console.log(`데이터 저장 완료!!! : ${JSON.stringify(data)}`);
});
// PUT : 전체 수정
id: 1,
userId: 1,
title: "변경제목",
body: "내용을 변경합니다.",
}).then((data) => {
console.log(`데이터 수정 완료!!! : ${JSON.stringify(data)}`);
});
// PATCH : 일부분 수정
title: "제목을 변경했습니다.",
body : "내용을 변경했습니다."
}).then((data) => {
console.log(`일부분 수정 완료!!! : ${JSON.stringify(data)}`);
});
// DELETE : 삭제
.then((data) => {
console.log(`삭제 완료!!! : ${JSON.stringify(data)}`);
});
</script>
</head>
<body>
</body>
</html>
|

'Front End > Javascript' 카테고리의 다른 글
| [JavaScript] Fetch API 사용해보기 (1) | 2024.11.08 |
|---|---|
| [JavaScript] JSON-SERVER 사용해 보기 (4) | 2024.11.07 |
| [JavaScript] Promise란? (0) | 2024.11.06 |
| [JavaScript] Synchronous(동기)/비동기(Asynchronous) (4) | 2024.11.06 |
| Hook 이란? (3) | 2024.11.06 |