Promise에 동기적 감성을 입히다 async/await

조 은길·2022년 6월 12일
0

Javascript 정리

목록 보기
29/48
post-thumbnail
post-custom-banner

지난 시간에 콜백함수보다 개선된 디자인 패턴인 Promise에 대해서 알아봤다.

let 프로미스 = new Promise(function(성공, 실패){
  setTimeout(function(){
    성공();
  }, 1000);
});

프로미스.then(function(결과){
  console.log('성공했어요');
}).catch(function(){
  console.log('실패했어요');
});

하지만, Promise가 어렵다면 그보다 훨씬 쉽게 쓸 수 있는 ES8(2017) 문법이 있다.

async, await이라는 키워드인데 각각 Promise와 then을 매우 쉽게 만들어주는 문법이다.


async 키워드를 쓰면 Promise 객체가 생긴다

함수를 하나 만든다고 치자!!

function 더하기 (){
  1 + 1 
}

이 때, 연산이 성공적으로 끝나고 특정 코드를 실행하고 싶다면, 어떻게 할 수 있을까?

콜백 함수를 디자인할 수있을 거다.

function 더하기 (){
  1 + 1 
  콜백();
}

더하기(어떤함수);

그러면, 더하기(); 함수를 실행하고 나서 그 자리에다가 뭔가 함수를 또 입력해가지고 순차적으로 실행할 수있을 거다.


더하기(어떤함수);
// 1    2

더하기 함수를 실행하고, 2번 함수를 실행해주세요.

그런데, 이런 콜백 패턴이 길어지는게 싫어서 Promise 디자인 패턴으로


더하기().then(function(){

})

이렇게 만들고 더하기() 함수를 Promise로 만들어내면 된다.
=> 더하기()가 하는 연산을 Promise 안에다가 집어넣든가 하면 된다.

근데, 이러면 Promise라는 걸 하나 또 만들어야 되잖아요!!

그게 귀찮다. 그러면, function에다가 async라는 키워드 하나만 붙이면 된다.

이것은 함수 선언 앞에다가만 붙일 수 있는 특별한 키워드이다.

화살표 함수 앞에다가 붙여도 된다.

async를 function 앞에 붙이면, 함수가 Promise 역할 가능

async 함수 실행후에는 Promise 객체가 남는다.

더하기().then(function(){
 // 이 자리에 함수 실행 후, Promise 객체가 남는다는 말이다.
})

Promise 객체가 남으면, 뭘 할수 있죠??

.then() 구문을 쓸 수 있다.

// 이제 콜백 함수나 Promise 같은 거 디자인 안 해도
// .then 체인을 사용할 수있다.
async function 더하기(){
  1 + 1 
}


더하기().then(function(){
	console.log('성공');
})

async만 붙이면, Promise 객체를 뱉어내고, Promise처럼 .then()을 붙일 수가 있다.


async 함수에 return을 붙이면??


async function 더하기(){
 return 1+1; // 결과를 출력해줄 수 있다 
}

더하기().then(function(결과){
	console.log(결과);
});

이게 async라는 최신 문법의 사용법 이라고 보면 된다.


async의 단점

  • 성공만 판정할 수 있다.
    • resolve, reject 같은 파라미터가 들어가는게 없다보니까
      실패의 경우, 특정 코드 실행같은 case는 만들어줄 수 없다.
    그러나, 강제로 보내줄 수는 있다.

async function 더하기(){
 	return Promise.reject('실패임'); 
  	// 이러면, .then 구문은 더이상 실행되지 않는다.
    // 에러를 뿜어냄!!
}

더하기().then(function(결과){
	console.log(결과);
})

async function 안에서 쓰는 await

내가 Promise를 정확히 잘 쓰고 싶어서, 함수 안에서 그냥 Promise를 직접 디자인한다고 가정해보자!!

async function 더하기 () {
  let 프로미스 = new Promise(function(성공, 실패){
  	let 힘든연산 = 1+1;
    성공();
    // 이렇게 성공 판정을 내리면,
    // .then 으로 넘어가고 잘 끝나기는 한다.
  })
}

그런데, .then 보다 async 안에서는 Promise를 좀 더 깔끔하게 쓸 수있다.

물론, async 안에 .then을 넣을 수도 있다.

async function 더하기 () {
  let 프로미스 = new Promise(function(성공, 실패){
  	let 힘든연산 = 1+1;
    성공();
    // 이렇게 성공 판정을 내리면,
    // .then 으로 넘어가고 잘 끝나기는 한다.
  });
  
  ////// 이 부분을 좀 더 예쁘게 쓸 수있다.
  프로미스.then(function(){
  	console.log('성공했어요');
  });
}
////////

더하기();

then() 함수가 귀찮다면, await 키워드를 쓸 수 있다

.then()과 똑같은 의미로 쓸 수있는 키워드가 바로 await이라는 키워드이다.

JS에서는 어렵고 힘든 연산을 만나면, 비동기식으로 처리해줄 수있다.
=> 약간 제겨두고 무언가를 실행시켜줄 수 있다는 뜻이다.

그런데, 그렇게 비동기식으로 처리되는 코드들 위아래에 뭔가 순차적으로 실행되는 코드가 있다면, 실행결과가 꼬이는 수가 있다.
=> 즉, 뭐가 먼저 실행될지 알 수 없다는 말이다.

이럴때, await이라는 키워드를 쓰면, 순서를 개발자들이 의도한대로 컨트롤할 수가 있다.

await은 " Promise가 해결되기를 기다려~ " 라는 뜻이다.

JS는 코드가 위에서부터 한 줄 한 줄 실행하다가,

let 결과 = await 프로미스; 를 만나면, await을 발견하는 순간, 비동기 처리하지 않고 기다린다. 프로미스가 해결될 때까지...

그래서, 성공이든 실패든 뭔가 판정이 나오기를 기다린다.

그래서, " 성공 판정이 나왔다 " 하면, 그 결과를 변수에 할당해준다.

  • await을 이용해서, 프로미스 연산 결과를 변수에 할당하는 게 가능하다

(주의) 비동기식처리되는 코드를 담는다면 await 기다리는 동안 브라우저가 잠깐 멈출 수 있다.

날씨API Sprint 할 때, json이라는 변수에 undefined 나왔던거 생각하면, await 키워드를 사용하면, 값이 제대로 나오겠다.

  • 결론
    .then이라는 구문보다 await이 직관으로 이해하기 더 쉽고, 파이썬 같은 다른 언어를 다루는 개발자들이 봐도 납득이 가는 방식이다.
    let 결과 = await 프로미스;
    => undefined가 안 나오고, 값이 할당될 때까지 기다림

await은 async라는 함수 안에서만 쓸 수있는 키워드이다.


기능을 만들때, await을 쓰고 싶다면??

그러면, async라는 걸 함수에 써보자!!

  • 현재 코드들이 함수가 아니라면??
    • 코드들은 함수로 묶어서 실행하면 된다.

await라는 키워드는 익숙해지면, 되게 쉽다.
=> 개발자가 Promise를 조금 더 예쁘게 다룰 수가 있다.

let 결과 = await 프로미스;
console.log(결과);

프로미스.then(function(){
	console.log("성공");
});

의 간략한 버전 그 이상도 이하도 아니다.

무슨 신기술 같이 전혀 특별한 게 아니다.

그런데, await에 실패값을 출력해보자!!


await은 프로미스 실패시, 에러가 나고 코드가 멈춘다

Promise가 실패하는 하단 코드를 실행해봅시다.

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  var 결과 = await 어려운연산;
  console.log(결과);
}
더하기();

어려운연산이라는 Promise가 실패할 경우

await 어려운연산이라는 코드는 에러가 나고 코드실행을 멈춘다.

그럼 await 하단에 있는 코드들은 더 이상 실행이 되지 않겠죠.

그래서 Promise가 실패할 경우

코드실행을 멈추고 싶지 않으면 약간 특별한 방법이 필요하다.

async function 더하기(){
  var 어려운연산 = new Promise((성공, 실패)=>{
    실패();
  });
  try {  var 결과 = await 어려운연산 }
  catch { 어려운연산 Promise가 실패할 경우 실행할 코드 }
}

try catch라는 자바스크립트 문법인데,

try {} 안의 코드가 에러가 나고 멈출 경우

대신 catch {} 내부의 코드를 실행해준다.

try{
 이걸 해보고 
}

catch{
 
  안되면(에러가 나면),
  이걸 실행해주세요~
  
}

try/catch 구문으로 싸매면, 코드 멈춤없이 실행할 수있다.

try{
 let 결과 = await 프로미스;
  console.log(결과);
}

catch{
 console.log('프로미스 연산이 잘 안되었군요');
}

try/catchawait과 자주 함께 쓰이는 문법이다.

이렇게 에러처리를 하실 수 있다.

Promise -> .then 잘 쓰고 있는데, 구지 이거 써야 돼??

안 써도 된다.

더 복잡하니까 그냥 then() 이런거 쓰셈!!

근데, await 이라는 키워드가 조금 더 보기가 쉽잖아요.

어려운연산이라는 Promise가 실패()가 안날거라고 자신하면 try/catch를 굳이 쓸 필요는 없으니 코드가 더 간단해질 수도 있다.


예제 : <button>을 누르면 성공하는 Promise 만들기

Q. HTML 페이지 내에 버튼 아무거나 하나 만들고 그걸 클릭하면 성공하는 Promise를 만들고 싶습니다.

성공하면 콘솔창에 '성공했어요'를 출력하고요.

어떻게 코드를 짜면 될까요?

(async, await이 필요하면 써봅시다)

  • 정답
<button id="test">버튼</button>

<script>
  var 프로미스 = new Promise(function(성공, 실패){
      document.getElementById('test').addEventListener('click', function(){
        성공();
      });
  })
  async function 버튼누르기(){
    var 결과 = await 프로미스;
    console.log('성공했으요')
  }

  버튼누르기();
</script>

전 이렇게 짰습니다.

  1. 일단 위의 버튼을 누르면 성공판정을 내리는 Promise를 만들었습니다.

그건 별거 아닙니다.

  1. 근데 이제 그게 성공하면 console.log()를 해주는 코드를 짜려고 봤더니 then을 쓰기 싫어서

await 프로미스; 이렇게 작성했습니다.

  1. 근데 await 을 쓰려면 async functinon 안에서만 쓸 수 있댔죠?

그래서 await 프로미스; 코드를 async function을 하나 만들어서 감쌌을 뿐입니다.

혹은 이렇게 짜셨을 수도 있겠군요.


<button id="test">버튼</button>

<script>
  async function 프로미스(){
    document.getElementById('test').addEventListener('click', function(){
      return '성공했어요'
    });
  }

  async function 버튼누르기(){
    var 결과 = await 프로미스();
    console.log(결과)
  }

  버튼누르기();
</script>

▲근데 위의 코드는 잘 동작하지 않습니다.

async가 Promise를 퉤 뱉는다고해서 async function 프로미스() 를 쓰고 이벤트리스너를 안에 담긴 했는데

버튼 누르면 return 어쩌구에 의해서 성공판정이 될거라고 기대했지만 안됩니다.

실은 return '성공했어요' 이게 async function의 return이 아니고 이벤트리스너안의 함수의 return문이라 문제되는 것도 있지만

그것보다 더 중요한 문제는 이겁니다.

  1. 이벤트 리스너안의 코드는 바로 실행되지 않습니다. 버튼 누를 때 실행됩니다.

  2. 그래서 컴퓨터가 코드를 쭉 읽을 때 async function 프로미스() 함수 내부는 빈칸과 동일합니다.

  3. 자바스크립트는 function 안이 빈칸이면 그냥 자동으로 return undefined 를 채워 실행합니다.

(그럼 3번에 의해서 async function 프로미스()는 0초만에 자동으로 성공()판정이 됩니다)

그래서 하단의

var 결과 = await 프로미스();

이 코드는 프로미스()가 0초만에 성공판정이 내려진 상태로 실행되며 (그 함수에 return undefined가 자동으로 채워지니까요)

var 결과 = undefined 와 동일한 뜻입니다.

그래서 코드가 이상해진 것입니다.

하지만 Promise로 만들어서 직접 성공(), 실패() 경우를 지정해준다면

await이 잘 기다려줍니다.

  • 구지 이렇게 만들어야 하나??

구지 필요는 없다. 그냥 콜백함수로 잘 해결할 수 있다.

BUT, 나중 가서 필요한 상황이 올 수 있다.

코드가 길거나 복잡해지면, async & await이 조금 더 유용해질 수 있다.

특히, 순차적으로 많은 것을 실행하고 싶을 때, async & await은 유용하다.

profile
좋은 길로만 가는 "조은길"입니다😁
post-custom-banner

0개의 댓글