자바스크립트의 Promise 문법 알아보기
1. Promise 문법이란?
Promise 는 비동기 작업의 상태 및 결과를 나타내는 객체다.
자바스크립트에서 ① 비동기 작업을 처리하고, ② 성공(resolve) 또는 실패(rejected) 상태를 처리할 수 있는 구조를 제공한다.
라고 하면 처음 봤을 때 이해가 하나도 안 되니까 쉽게 풀면...
① 비동기 작업 → 연산이 시작되고 끝나는 순서가 정해진 건 아니다.
② 성공 또는 실패 상태 처리 → 성공하면 resolve() 가 호출, 실패하면 reject() 가 호출된다.
③ 객체 → 익명 함수를 품고 있어서 그렇다.
이정도가 되겠다.
정리하면...
익명함수를 품고 있는데, 그 익명함수를 실행했을 때 성공하면 resolve 호출, 실패하면 reject 함수를 호출하는 객체 라고 할 수 있겠다. 근데 이제 딱히 연산 순서가 정해진 작업은 아니라서 실행순서와 관계없이 빨리 연산된 순서대로 결과가 나오기도 하는.
2. Promise 객체의 생성구조
① Promise 객체가 익명함수 하나를 품고 있다.
new Promise( ()=>{} );
② 생성된 객체를 참조변수(pm)에 저장한다.
const pm = new Promise ( ()=>{} );
- 이러면 pm 안에는 익명함수에 대한 실행 결과 값이 저장되는 것이다.
③ 안에 있는 익명함수는 resolve, reject 라는 매개변수가 존재한다.
const pm = new Promise( (resolve, reject)=>{} );
④ 매개변수에는 함수가 전달되어서 매개변수 이름으로 전달된 함수를 호출할 수 있다.
const pm = new Promise( (resolve, reject)=>{
resolve(); // 또는 reject();
} );
⑤ 대개 익명함수가 정상실행 되었다면 resolve() 가 호출되고, 그렇지 않으면 reject() 가 호출된다.
이를 모두 종합한 일반적인 예시는 아래와 같다.
const pm = new Promise( (resolve, reject) =>{
// 명령1, 명령2...
if( /* 조건 */ true /* or false */ ) {
resolve();
}else{
reject();
}
});
3. Promise 객체의 결과를 받아 사용하는 방법
const pm = new Promise( (resolve, reject) =>{
if( true ) {
resolve('성공');
}else{
reject('실패');
}
});
위와 같은 Promise 객체가 존재한다고 해보자.
물론 예시의 경우 if 조건이 true 이기 때문에 reject('실패'); 함수가 호출되는 일은 없긴하다.
결과를 사용하는 순서는 다음과 같다.
① Promise 객체 안에 있는 익명 함수의 실행 결과를 pm 변수에 담는다. (선택)
② 결과를 지닌 참조변수 pm 에 .then() 함수와 .catch() 함수를 붙여준다.
이때 then() 은 성공(resolve) 했을 때 호출되고, catch() 는 실패(reject) 했을 때 호출된다.
▶ 형태1
pm.then() // resolve() 가 호출되었을 때 사용할 함수
pm.catch() // reject() 가 호출되었을 때 사용할 함수
직관적으로 보면 이런 형태다.
그런데 보통은 pm 을 각각 참조하지 않고 아래와 같이 체이닝 방식을 통해 연결해서 사용한다.
pm
.then( ()=>{} ) // resolve() 가 호출되었을 때 사용할 함수
.catch( ()=>{} ); // reject() 가 호출되었을 때 사용할 함수
Promise 객체를 사용할 때는 이런 형태를 만들어 사용한다.
이렇게 실행결과가 담긴 참조변수 pm 의 결과에 따라 .then() 혹은 .catch() 함수를 실행되는 것이다.
다시 말해 Promise 객체에서 resolve() 가 호출되면 .then() 함수가 실행되고, reject() 가 호출되면 .catch() 함수가 실행되는 방식이다.
▶ 형태2
따로 객체에 받지 않고도 실행할 수 있다.
new Promise((resolve, reject)=>{
if(true){
resolve('성공');
}else{
reject('실패');
}
})
.then((msg)=>{ console.log('pnm 없이', msg) } )
.catch((msg)=>{ console.log('pnm 없이', msg) } );
▶ 예시1
const pm = new Promise( (resolve, reject) =>{
if( true ) {
resolve('성공'); // 실행
}else{
reject('실패');
}
});
pm
.then( (message)=>{
console.log(message); // 실행
})
.catch( (message)=>{
console.log(message);
});
처음에 예시로 든 Promise 객체를 가져와서 전달 받은 메시지를 출력하는 구조로 짜봤다.
이와 같은 형태라면
① pm 에는 Promise 의 익명함수 실행 결과가 담기게 되는데
② if 문의 조건이 true 이니 resolve('성공'); 이 담기게 될 것이고
③ resolve() 가 실행됐으니 pm.then() 함수가 실행된다.
④ then 함수에서 message 를 받아 console 에 출력하면? 성공 이란 메시지가 뜬다.
▶ 예시2
const con = false; // 특정 상황을 만들기 위한 상태값
const pm5 = new Promise( (resolve, reject)=>{
if( con ){
resolve("condition 값은 true 입니다.");
}else{
reject("condition 값은 false 입니다."); // 실행
}
});
pm5
.then( (msg)=>{ console.log(msg); } )
.catch( (msg)=>{ console.log(msg); } ) // 실행
.finally( ()=>{ console.log('Promise 가 종료되었습니다.'); } ); // 실행
비슷한 방식이다.
① pm5 에는 Promise 의 익명함수 실행 결과인 reject(); 가 담기게 되고
② pm5.catch() 로 reject() 함수를 실행한다.
③ catch 함수가 실행되어 msg 를 console 에 출력한다.
④ 함수가 종료되면 무조건 실행되는 .finally 함수도 catch() 실행 이후 실행된다.
4. Promise의 특징 '비동기실행'
동기실행, 비동기실행이 뭐냐고 물어본다면 '실행한 순서대로 결과가 반환되는지'의 여부라고 할 수 있겠다.
실행 순서대로 결과 반환까지 진행되면 동기, 실행 순서와 관계없이 빨리 연산되는 순서대로 결과가 반환되면 비동기실행이다.
이를 간단한 예시로 알아보자.
▶ 동기실행
/* 동기실행 */
console.log('====== 작업 시작 ======');
console.log('작업1 시작');
const wakeUpTime = Date.now() + 1000; // 현재시간을 얻어서 1초를 더한 시간 계산
// 반복실행동안 연속해서 현재 시간을 얻으면서 아무것도 안 하는 반복 실행을 진행
while(Date.now() < wakeUpTime){ } // 작업 1에 1초간 긴 작업시간 포함
console.log('작업1 종료');
console.log('작업2 시작');
console.log('작업2 종료');
console.log('====== 모든 작업 종료 ======');
작업 1이 시작되고 1초의 텀을 둔 뒤 '작업1 종료', '작업2 시작', '작업2 종료' 를 순차적으로 출력한 코드다.
이렇게 실행 순서대로 결과가 반환되는 것이 동기실행이다.
▶ 비동기실행
/* 비동기실행 */
// setTimeout, setInterval 등 전체 시작과 상관없이 별도로 진행
function longRunningTask(){
console.log('작업1 시작');
const wakeUpTime = Date.now() + 1000;
while(Date.now() < wakeUpTime){ } // 1초 딜레이
console.log('작업1 종료');
}
console.log('====== 작업 시작 ======');
setTimeout( longRunningTask, 0 ); // 작업2 보다 작업1 을 먼저 실행
console.log('작업2 시작');
console.log('작업2 종료');
console.log('====== 모든 작업 종료 ======');
// 그럼에도 불구하고 작업2의 시작과 종료가 먼저 이루어짐
비동기 실행의 대표적인 setTimeout 함수를 사용했다.
(비동기실행의 대표적인 예로 setTimeout, setInterval, Promise 가 있다.)
마찬가지로 '작업1 시작' → '작업1 종료' → '작업2 시작' → '작업2 종료' 순서대로 실행을 했다.
함수를 실행하는 게 연산이 더 오래걸렸는지 어쨌는지 모르겠으나 결과는 뒤죽박죽이 되었다.
이게 비동기실행이다.
다시 돌아와서,
Promise 도 실행순서와 상관없이 결과를 반환하는 비동기 실행이다.
▶ Promise 의 비동기실행
/* Promise 를 이용한 비동기실행 */
let pm = new Promise((resolve, reject)=>{
console.log('====== 작업 시작 ======');
resolve('작업1'); // 비동기 실행할 내용의 호출
console.log('작업2 시작');
console.log('작업2 종료');
console.log('====== 모든 작업 종료 ======');
});
// 다른명령
// 다른명령
// 다른명령
pm
.then((msg)=>{
console.log(msg, '시작');
const wakeUpTime = Date.now() + 1000; // 1초 딜레이
while(Date.now() < wakeUpTime){ }
console.log(msg, '종료');
});
작업 시작과 동시에 resolve 함수의 매개변수로 '작업1' 텍스트를 보냈다.
그리고 pm.then 이 실행되면서 '작업1' 을 받아
'작업1 시작' → (1초 딜레이 이후) → '작업1 종료' 문자를 console 에 남긴다.
실행 순서 자체는 마찬가지로 '작업1 시작' → '작업1 종료' → '작업2 시작' → '작업2 종료' 다.
하지만 역시나, 비동기실행이라 실행순서와 관계없이 결과를 반환한다.
5. 동기 실행처럼 보이게 하는 async / await
이런 비동기 실행을 하는 Promise 를 가지고 동기 실행을 해야할 때가 있을 수 있다.
물론, 기존의 방법으로도 가능은 하다.
/* promise 를 이용한 비동기실행에서 두 작업의 실행순서를 맞춰야 할 때 */
let pm = new Promise((resolve, reject)=>{
console.log('===== 작업 시작 =====');
resolve();
});
pm
.then(()=>{
console.log('작업1 시작');
const wakeUpTime = Date.now() + 3000;
while (Date.now() < wakeUpTime){}
console.log('작업1 종료');
return new Promise((resolve, reject)=>{
resolve();
});
})
.then(()=>{
console.log("작업2 시작");
console.log("작업2 종료");
})
단지 이정도까지? 란 생각이 들 뿐.
resolve 안에 resolve 를 넣어 .then 함수를 두번 호출하는 형태다.
결과가 동기 실행처럼 나오기는 했다.
이런 방법 말고, Promise 에는 async / await 를 이용해 동기처럼 보이도록 실행하는 방법이 존재한다.
정확히는, await 를 통해 함수 실행이 마무리될 때까지 기다리는 방식이다.
▶ async / await 방식
async / await 는 자바스크립트에서 Promise 를 더 간결하고 읽기 쉬운 방식으로 처리할 수 있도록 도와주는 문법이다.
순서없이 끝나는 다중 작업을 원하는 순서대로 맞춰 종료하고자 한다면(= 동기실행 하고 싶다면)
async 로 생성한 비동기 함수에 비동기 호출을 await 로 호출하고, 다른 명령들을 쭉 - 작성해준다.
await 키워드가 붙으면 Promise 가 해결될 때까지 기다렸다가 결과를 반환하기 때문에 결과적으로는 비동기 실행이라도 순차적으로 실행되는 것이다.
function funcPromise(){ // 실행순서 #3
const pm = new Promise( (resolve, reject)=>{
console.log('작업1 시작'); // 실행순서 #4
resolve('작업1 종료'); // 실행순서 #5
});
return pm; // Promise 반환
}
async function func1(){
try{
// await 사용 시, then 은 사라지고 변수에 넣을 수도 있음
let result = await funcPromise(); // 실행순서 #2 (Promise 결과 반환 기다림) -> 실행순서 #6
console.log(result); // 실행순서 #7 -> resolve()
console.log('작업2 시작'); // 실행순서 #8
console.log('작업2 종료'); // 실행순서 #9
}catch(err){
console.error(err);
}
}
func1(); // 실행순서 #1
async - await 를 활용한 예시다.
이를 간단히 요약하자면...
① 비동기 실행이 이루어지는 func1 앞에 async 키워드가 붙는다.
② 동기적으로 처리할 funcPromise 함수를 await 키워드를 붙여 호출한다.
③ 결과를 반환할 때가지 다른 작업은 진행되지 않는다.
이정도겠다.
본래라면 Promise 는 비동기 실행이기 때문에 result 가 나오기도 전에 밑에 코드들이 실행되었겠지만.
funcPromise() 앞에 await 키워드가 붙어있어 funcPromise() 가 결과를 반환할 때까지 모든 작업을 멈춘다.
funcPromise() 에서 반환된 결과는 변수에 담을 수 있게 되며, result 에 값이 들어오고 나서야 비로소 console.log(result); 가 실행된다.
▶ await 반환과 예외처리
async/await 방식으로 동기처리를 하면서 await 로 호출한 함수를 변수에 담았다.
await 는 비동기 작업이 끝난 후 '결과를 반환' 한다고 했으니 호출한 함수의 반환값은 Promise 의 결과값일텐데,
여기에 담겨진 함수는 resolve 일까, reject 일까?
결과부터 말하면 resolve 다.
async function func1(){
try{
let result = await funcPromise(); // 비동기 호출을 await 로 호출
// result 가 resolve 반환 시 실행
}catch(err){
// result 가 reject 반환 시 실행(예외처리)
}
}
await 를 사용할 때 try-catch 문으로 감싸준 이유가 여기서 나온다.
await 는 reject 를 예외로 간주하기 때문에, reject 가 반환되면 catch 가 실행되면서 에러 처리가 된다.
그렇기 때문에 await 에서 예외 처리는 필수다!
await 밑으로 존재하는 연산을 처리하기 위해서는 Promise 가 resolve(성공) 를 반환해야 하는 것이다.
▶ async 반환값
비동기 실행이 이루어지는 함수 앞에 async 가 붙는다는 것은 async는 비동기 함수임을 나타내는 키워드라는 말이된다.
이렇게 비동기 함수 임이 선언된 async 가 붙은 함수는 반드시 Promise 를 반환한다.
async function func1(){ // 순서대로 맞춰 종료하고자 하는 함수를 async 로 생성
try{
let result = await funcPromise(); // 비동기 호출을 await 로 호출
console.log(result);
console.log('작업2 시작');
console.log('작업2 종료');
}catch(err){
console.error(err);
}
}
const returnValue = func1();
console.log(returnValue); // Promise { <pending> }
returnValue.then(() => console.log('func1 완료')); // 'func1 완료' 출력
전체코드
const returnValue = func1();
console.log(returnValue); // Promise { <pending> }
async 함수 반환
async 함수로 선언된 func1 의 반환값을 받아 출력하면 Promise { <pending> }
즉, Promise 가 반환된다.
위와 같이 Promise 가 반환됐다는 것은 아직 Promise 가 resolve 되지 않았음을 뜻한다.
returnValue.then(() => console.log('func1 완료')); // 'func1 완료' 출력
async 함수에서 반환된 Promise 를 해결
이렇게 반환된 Promise 의 반환값을 보기 위해 .then() 을 붙이면
Promise 가 해결(resolve) 되면서 resolve() 가 호출되어 'func1 완료' 가 출력된다.
'Frontend > JavaScript' 카테고리의 다른 글
[JavaScript] form 안에 같은 name 의 버튼(radio/checkbox) 값을 구분하기 (0) | 2024.11.14 |
---|