Dev/SW Engineering

14. Asynchronous & Promise - Promise& Async/Await

HJChung 2020. 7. 21. 12:45

+ Promise& Async/Awai t에 대해 공부한 것을 정리한 것입니다. 배우는 중이라 잘못된  내용이 있을 수 있으며 계속해서 보완해 나갈 것입니다. :)) 

 

이전 포스트에서 '비동기 처리 로직을 위해 콜백함수를 연속해서 사용할 때 발생하는 콜백지옥'을 마지막으로 정리해보았다.

콜백 지옥을 해결하기 위해서는 Promise & Async/Await 방식을 사용할 수 있다.

1. Promise

1) Promise란

비동기 처리에 사용할 수 있는 내용이 실행은 되었지만 결과를 아직 반환하지 않은 객체이다. 

 

2) Promise의 필요성 

1. callback을 이용한 비동기 함수의 전달 방법

3초 후에 실행되는 어떤 코드를 작성해야한다고 할 때, promise를 사용하지 않았을 때와  사용했을 때의 코드를 비교해보자. 

① promise를 사용하지 않는 경우 ( callback을 이용한 비동기 함수의 전달 방법)

function callback(){ /*실행 해야하는 코드 */ }
setTimeout(()=>{callback}, 3000)

이런식으로 콜백함수를 사용해야 할 것이다. 즉, callback이 항상 setTimeout 내에 있어야 해서 밖으로 꺼낼 수 없다.

② promise를 사용한 경우 

const promise = setTimeoutPromise(3000)

console.log('딴짓')
console.log('딴짓')
console.log('딴짓')

promise
.then(()=> {
	/*실행 해야하는 코드 - 지금 실행해*/
})
    

이렇게 함수를 분리해서 작성할 수 있다는 것이 promise의 큰 장점 중 하나이다.

2. callback의 중첩

① promise를 사용하지 않는 경우 ( callback을 이용한 비동기 함수의 전달 방법)

function findAndSaveUser(Users){
	Users.findOne({}, (err, user) => { //첫 번째 콜백
    	  if(err){
        	  return console.error(err);
          }
          user.name = 'zero'
          user.save((err) => { //두 번째 콜백
        	  if(err){
            	  return console.error(err);
               }
               Users.findOne({gender: 'm'}, (err, user) => { //세 번째 콜백
             	  //.....
                });
          //.....
           
 //코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

이렇게 콜백이 여러개 중첩되어야하는 콜백지옥 상황이 온다. 

② promise를 사용한 경우 

function findAndSaveUser(Users){
	Users.findOne({})
    	.then((user) => {
        	user.name = 'zero';
            return user.save()
        })
        .then((user) => {
        	return Users.findOne({gender:'m'})
         })
        .then((user) => {
        	//.....
        })
        .catch((err) => {
        	//
        }
    })
    
 //코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

3) Promise의 사용

Promise를 사용하는 비동기 처리 함수는 callback을 사용하지 않고, Promise 객체를 return 한다.

그리고 이 Promise instance는 resolve과 reject를 인자로 가지는 자신만의 callback함수를 실행한다. 

const condition = true;
const promise = new Promise((resolve, reject) => {
//4~9번 째 줄까지는 동기로 실행
	if(condition){ 
    	resolve('성공');
    }else{
    	reject('실패');
    }
 }); //그리고 이 실행된 결과를 promise가 들고 있다가, 
 
//다른 코드가 들어갈 수 있음
//promise 실행
promise //이렇게 원하는 시점에 .then과 .catch를 통해 사용할 수 있다. 
	.then((message) => { //성공시
    	console.log(message);
    })
    .catch(error) => { //실패시
    	console.log(error);
     }
 })
 
 
 //코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

4) Promise Prototype

  • Promise.resolve(성공 리턴값): 바로 resolve 하는 프로미스 => 실행이 완료되면 .then 내부 함수가 실행된다. 
  • Promise.reject(실패 리턴값): 바로 reject 하는 프로미스 => .catch 내부 함수가 실행된다
  • Promise.all(배열) : 여러 개의 프로미스를 동시에 실행하고, 하나라도 실패하면 catch로 간다. 
  • Promise.allSettled(배열): 실패한 것만 추려낼 수 있다. 

ex)

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');

Promise.all([promise1, promise2])
	.then((result) => {
    	console.log(result);//['성공1', '성공2']
     })
     .catch((err) => {
     	console.error(err);
     }
   });
   
 //코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await    

5) Promise Hell과 Promise Chaining

function gotoCodestates() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('1. go to codestates') }, Math.floor(Math.random() * 100) + 1)
    })
}

function sitAndCode() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('2. sit and code') }, Math.floor(Math.random() * 100) + 1)
    })
}

function eatLunch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('3. eat lunch') }, Math.floor(Math.random() * 100) + 1)
    })
}

function goToBed() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('4. goToBed') }, Math.floor(Math.random() * 100) + 1)
    })
}

gotoCodestates()
.then(data => {
    sitAndCode()
    .then(data => {
        eatLunch()
        .then(data => {   
            goToBed()
            .then(data => {
                console.log(data)
        
            })
        })
    })
})

 이런 코드가 있을 때 Promise 실행 부분을 보면 callback hell에서 봤던것 같은 형태의 Promise hell이 발생하기도 한다. 그래서 적절하게 reeturn을 해줌으로써 Promise chaining으로 여러개의 프로미스를 깔끔하게(?) 연결할 수 있다. 

gotoCodestates()
.then(data => {
    return sitAndCode()
})
.then(data => {
    return eatLunch()
})
.then(data => {
    return goToBed()
})
.then(data => {
    console.log(data)
})

 

지금까지 Promise와 사용에 대해서 알아보았다. 

그런데 Promise를 사용해도 마지막인 .then .then .then.... 의 반복이고 Callback이든 Promise든 '지옥' 이 존재했다. 

Javascript의 비동기 처리 패턴 중 가장 최근에 나온 async & await는 Callback이든 Promise든 '지옥' 단점을 보완할 수도 있고, 

비동기 처리 함수들을 마치 동기인 것 처럼 작성할 수 있다. 

그래서 바로 위의 코드를 async & await를 사용해서 작성하면, 

const result = async () => {
    const one = await gotoCodestates();
    console.log(one)

    const two = await sitAndCode();
    console.log(two)

    const three = await eatLunch();
    console.log(three)

    const four = await goToBed();
    console.log(four)
}

result();

이렇게 된다. 

async & await에 대해서 더 정리해보자. 

2. async/await

1) async/await의 사용

앞에서 살펴본 예제에서 async/await을 써 보자. 

 promise를 사용한 경우 

function findAndSaveUser(Users){
   Users.findOne({})
    	.then((user) => {
        	user.name = 'zero';
            return user.save()
        })
        .then((user) => {
        	return Users.findOne({gender:'m'})
         })
        .then((user) => {
        	//.....
        })
        .catch((err) => {
        	//
        }
    })
    
//코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

② async/await을 사용한 경우

await literally suspends the function execution until the promise settles, and then resumes it with the promise result

async function findAndSaveUser(Users) {
    let user = await Users.findOne({}); //비동기 처리의 결과 값을 user에게 준다.
    //await literally suspends the function execution until the promise settles, and then resumes it with the promise result
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({gender:'m'});
 }
 //코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

즉, async/awiat 역시 promise를 반환하기 때문에 async 함수에서 return 해준 값은 .then이나 await으로 받아야한다. 

ex) 

async function main(){
	const result = await promise;
    return 'zerocho';
}

main().then((name) => {
	//....
} //이렇게 then으로 받거나 또는

const name = await main()
//이렇게 await으로 받거나!

//코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

그리고 async 내에서 await을 사용하기도 한다. 

 

2) async/await의 결과 처리

.then 부분은 await을 사용하는데, .catch 부분은 어떻게 처리하는가? try{}, catch{}를 사용한다. 

ex)

async function main(){
   try{ 
        const result = await promise;
        return result;
   	}
    catch(err){ //reject 처리
    	console.error(err);
    }
}

//코드 출처: 제로초 님의 노드교과서 개정판 2-8. Promise, async/await

 

Reference

www.youtube.com/watch?v=JB_yU6Oe2eE

joshua1988.github.io/web-development/javascript/promise-for-beginners/#promise%EA%B0%80-%EB%AD%94%EA%B0%80%EC%9A%94

programmingsummaries.tistory.com/325a