1초 후에 어떤 함수를 호출하는 timer함수라는게 있다고 하자, 근데 여기서 끝나는게 아니라 저 콜백함수 안에 또 timer를 호출해야 한다고 했을때 그 안에는 또 timer함수가 들어갈것이고
이런식으로 비동기 방식으로 처리를 할 때 콜백이 많이 나오는데 무한이 반복되는 것을 흔히 콜백 지옥이라고 부른다.
이런 문제를 극복하기 위해 나온게 Promise이다.
timer라는 함수가 Promise로 나오면 우린 지난 시간에 배웠던것처럼 뒤에 .then()을 붙여 작업을 할 수 있다.
또한 작업이 끝나고 또 다시 timer를 실행하겠다고 하면 더 timer의 콜백함수의 return값에 다시 return timer(1000); 이런식으로 부르고 거기뒤에 .then을 또 붙여 체인방식으로 코드를 만들 수 있다.
이렇게 체이닝이 되면 함수 안에 함수가 계속 들어가는 콜백 지옥에서 벗어날 수 있게 된다.
여기서 우리는 저기있는 timer(1000)함수를 호출했을 때 저렇게 then()이라는것도 계속 이어 붙이기 싫다면?
이런식으로 timer(1000)을 호출하고 작업하고 또 호출하고, 작업하고
이런식으로 하고싶다면..? 이렇게 동기적으로 만들어진 코드가 비동기적으로 만들어진 코드와 똑같이 작동되게 하고 싶다면 어떻게 해야할까?
먼저 비동기적인 함수 앞에 이 함수가 실행되길 기다려라! 라는 await라는 것을 붙인다.
이 await이 붙어있는 Promise를 리턴하는 함수는 반드시 다른 함수 안에서 실행 되어야 한다.
그리고 그 함수는 async라는 키워드가 앞에 붙어 있어야한다.
실습을 위해
지난 시간에 배웠던 Promise를 이용해 timer함수를 만들었다. 이걸 실행하면
1초 후에 1초라는 시간이 리턴이 된다.
이번엔 기존의 시간에 1초라는 시간을 더해서 지연을 시켜보고 싶다.
이렇게 안에 timer를 한번더 호출하고 +1000을 더해주면 1초를 더해 지연 시킨것이 된다.
그리고 이 새로만든 timer도 Promise이기때문에 then을 붙일 수 있다.
실행시키면 1초뒤에 1초를 넣은 함수가 실행되고 2초 뒤에는 1초 뒤에 1000을 더한 함수가 실행이 된다.
한번 더 써서 3초까지 만들었다.
그럼 이때 이 일련의 작업의 시작과 끝에 start와 end라는 텍스트를 출력하고 싶다.
이렇게 코드 위아래로 start와 end를 넣으면 되는걸까?
실행해보았더니 예상했던 결과 값과 다른 결과가 나온다.
당연한 결과이다. 저기있는 console.log('start')가 실행이 되고 바로 아래 timer(1000)이 실행이 될 것이다. 근데 저 타이머는 비동기적인 작업이기때문에 실행이 예약 되고 자기 갈길 가는거다(1초뒤에 시간 출력) 그리고 그 시간을 전혀 기다리지 않고 바로 console.log('end')가 실행되기때문에 저런 순서로 실행이 되는것이다.
그럼 저 end가 타이머 뒤로 가려면 어떻게 해야할까?
이런식으로 마지막 then안에 넣어주면 우리가 예상하는 결과가 나올 것이다.
이것이 우리가 Promise를 사용하는 기본적인 방법이다.
이걸 동기적인 코드인것처럼 동작하게 만들어 보겠다.
먼저 start라는 텍스트를 그대로 복붙하고, 그 아래 timer(1000)함수를 넣는데 이 함수는 비동기적인 코드다라는것을 알려주기 위해 await라는 키워드를 붙였다.
자 그럼 이 타이머가 실행될 때 기본적이고 then안에 콜백 함수가 실행이 되고 그 콜백함수의 첫번째 파라미터로 결과값을 줄건데 그 결과 값은
이렇게 받을 수 있다.
다시 말하지만 then안에 콜백의 첫 번째 파라미터는 await를 쓰면 그냥 이 timer의 return 값으로 받을 수 있는 마법이다.
그 다음우리는 그 시간을 찍어보는 콘솔 console.log('time:'+time);을 작성했고 그 다음에 return timer(time+1000)을 작성했는데 요 리턴부분을 다시 return을 떼고 그대로 작성하고 또 앞에 await을 붙여준다. 그리고 그 결과를 다시 time으로 받고 콘솔로 뿌리는 작업을 똑같이 한다.
최종적으론
이런식으로 await을 이용하여 코드를 작성했다.
그러면 await를 이용하면 위에서 복잡하게 작성했던 Promise 코드를 비교적 간단하게 바꿀 수 있다.
이대로 저장해서 화면을 리로드 해보면
이런 에러를 확인할 수 있다.
해석을 하자면 await는 async함수 안에서만 사용할 수 있다는 뜻이다.
그래서 run이라는 이름의 함수 안에 저 코드를 넣어보았다.
그런데도 결과는 똑같이 나온다.
왜냐하면 저 함수는 async함수가 아니기때문이다.
이걸 해결하기 위해 함수 앞에 async라는 키워드를 붙여줘서 async함수로 만들고 다시 실행을 해보았다.
예상대로 출력이 되는 것을 확인할 수 있다.
여기있는 run()은 내부적으로 await를 가지고 있다. 비동기적으로 내부적으로 실행되는 함수이다.
그럼 여기서
이렇게 parent start와 end를 run함수 앞뒤로 붙이면 어떻게 될까?
먼저 가장 상단의 parent start가 실행이 되고 그 아래 run안에 start가 실행이 되고 timer 함수를 기다리지 않고 바로 parent end가 실행이 되었다. 즉 async가 붙어있는 저 함수는 비동기적으로 작동하고 있다는것. 그럼 어떻게 해야 또 parent end가 제일 마지막에 나올 수 있을까?
저 run()함수를 콘솔로 찍어보면 Promise를 반환하는데 이건 다시말해 async를 앞에 붙이면 그 함수는 Promise를 암시적으로 리턴한다. 그럼 이 Promise로 리턴 된다는 것은 뭐냐하면 그 앞에 또 await을 붙일 수 있다는 것이다.
이런식으로 붙일 수 있는데 이걸 실행하면 또 에러가 날 것이다. 왜냐하면 이 await도 async안에 들어가 있어야하기때문이다.
이런식으로 또 묶어서 실행을 하면
이렇게 순서대로 출력하도록 할 수 있다.
정리하자면 async라는 키워드는 평범한 함수를 Promise를 리턴하는 비동기적인 함수로 만들어주는 키워드이고 그렇기때문에 이 함수 안에서는 await를 사용할 수 있는것이다.
async라는 키워드는 함수를 비동기적으로(병렬적으로 다른 코드들이 실행이 되어도 나도 나만의 길을 걸으며 실행이 되는)만들어주는데 그 안에 await을 붙이면 동기적으로 순서대로 실행이 되도록 할 수 있는걸로 이해를 하였다.. 이 주제는 좀 어렵기때문에 나중에 다시 공부를 해봐야할것같다.
이 글은 유튜브 생활코딩 채널의 강의 영상을 보며 공부한 내용을 기록한 게시글입니다.