안녕하세요! 🩶 오늘은
1. 비동기 처리
2. callback 함수
3. Promise 객체
에 대해 하나하나 알아보겠습니다.
자바스크립트를 해오신 분들이시라면 아시겠지만, 자바스크립트에서는 코드를 위에서부터 아래로 순차적으로 실행합니다. 특정 작업이 완료될 때까지 다음 작업으로 넘어가지 않는 이러한 방식을 동기적 실행이라고 합니다. 자바스크립트는 기본적으로 동기적인 방법으로 실행됩니다.
동기 실행
: 특정 작업이 완료될 때까지 대기하며, 다음 작업으로 넘어가지 않는 방식 (순차적 진행)
비동기 실행
: 특정 작업이 완료되지 않아도 다른 작업을 병렬적으로 실행할 수 있는 방식
정해진 흐름에서 벗어난 동작을 하도록 만드는 방법
우리는 상황에 따라 동기와 비동기 프로그래밍을 적절히 사용할 수 있어야 합니다. 따라서 오늘은 비동기 프로그래밍을 할 수 있는 방법들에 대해 알아볼 것입니다. 이를 위해서는 callback 함수와 Promise 객체를 알아야 하는데요, 먼저 callback 함수부터 살펴보겠습니다.
callback 함수
: 인자(parameter)로 넘겨지는 함수.
나중에 호출(=callback) 하는 함수
말 그대로 인자로 넘겨진 후 나중에 호출되는 함수를 callback함수라고 합니다.
아래 코드를 통해 callback함수를 알아봅시다.
🐶🐱
setTimeout() 함수는 스케줄링 함수 중 하나인데요, 바로 특정 시간에 특정 함수를 실행하게 해 주는 메소드입니다. 스케줄링 함수들은 비동기 처리에서 자주 쓰이는 메소드입니다.(이후 포스팅에서 다룰 예정). setTimeout() 함수는 보통 1. 실행할 함수 2. 시간 . 이렇게 두 개의 매개변수를 갖습니다. 위 코드에서는 현재 매개변수로 함수와 3000이라는 숫자를 받았는데요, 이말인즉 해당 함수를 3초 후(3000=3초입니다.) 실행시키라는 의미입니다.
최종 코드를 보니 dog(cat); 즉 cat 함수를 인수로 둔 dog함수를 호출하고 있네요. 이처럼 함수를 인수로 넘겨주는 형태라면 넘겨지는 함수는 callback함수이기 때문에 나중에 다시 호출될 것입니다. 역시 dog 함수 내부를 보니 cat함수가 다시 호출되고 있죠. 결론적으로 dog 함수 호출로부터 3초 후 "강아지" 출력과 "고양이" 출력이 순서대로 일어나게 됩니다.
만일 아주 긴 코드가 있다고 가정해 봅시다. 동기식 코드와 비동기식 코드가 함께 섞여있을 때, 우리는 코드의 실행 순서를 제대로 정립할 수가 없어 혼란을 겪게 됩니다. 😵💫( 3초 후에 실행해야 하는 비동기식 코드와, 위치적으로 아래쪽에 위치한 동기식 코드 중 어느 코드가 먼저 실행될 것 같나요? 혼란스러우실 겁니다.) 하지만 callback 함수를 이용하면 비동기 처리가 끝난 후 해야 할 작업에 대해 순서를 보장해 줄 수 있게 됩니다. (위 코드에서) 비동기적으로 "강아지"가 출력되므로, 그 출력 시점이 언제인지 정확히 짚어내기는 모호하지만, 어찌됐든 "강아지"가 출력되면 바로 "고양이"가 뒤따라 출력되는 것만은 보장해주던 아까의 코드처럼 말이죠. 이처럼 우리는 비동기식 처리 안에서 코드의 실행 순서를 보장해주기 위해 callback 함수를 사용합니다. 나중에 실행되어야 할 함수라면 callback 함수로 넘겨주는 겁니다. ( callback 자체가 비동기적인 함수인 것은 아닙니다. )
하지만 callback함수에는 치명적인 단점이 있습니다.
callback함수 안에서 callback함수를 호출하는 일이 반복적으로 일어나고 있는 아래의 코드를 통해 문제점을 살펴봅시다.
결과 출력값을 알려드리겠습니다.
"시작"
(3초 후)
"1번 주문 완료"
(3초 후)
"2번 주문 완료"
(3초 후)
"3번 주문 완료"
"끝"
코드를 보면 함수 f1에서 콜백함수 f2를 호출하고, 콜백함수 f2에서 또 콜백함수 f3을 호출하고 있습니다.
비동기식 코드 속에서 실행 순서를 보장하기 위해 callback 함수 안에서 계속해서 callback함수를 호출했더니, 비록 제가 의도한 순서대로 실행하는 것은 성공했지만 코드가 많이 복잡해지고 가독성이 떨어져 보이네요. 이처럼 콜백 함수 안에서 계속해서 콜백 함수를 호출하는 것이 반복되고 있는 것을 두고 콜백 지옥(callback hell)이라고 합니다. 비동기 작업이 많이 포함된 코드에서는 콜백 함수의 중첩이 위 코드보다도 더 깊어질 수 있으며, 이로 인해 코드의 이해와 유지보수가 어려워지는 문제가 생깁니다. 😨
Q. 그럼 그냥 보기 쉽게 평소처럼 순차적으로 코드 쓰면 안되나요? 실행 순서도 중구난방이고 가독성도 떨어지는 비동기식 코드를 써야만 하는 이유가 뭔가요? 🫤
A. 💁🏻♀️ 물론 동기식으로 작성된 코드가 흐름을 파악하는 데는 용이할지 몰라도, 순차적으로 실행하는 데 걸리는 시간이 매우 오래 걸리는 경우라면 해당 코드의 실행을 마치기 전까지는 어떤 작업도 못하고 멈춰있어야 하는 비효율적인 상황에 직면해야 합니다. 이러한 상황들을 방지하기 위해서는 당연히 비동기식 프로그래밍이 필요합니다.
앞서 살펴본 callback함수의 단점을 보완할 수 있는 개념이 바로 우리가 지금부터 알아볼 "Promise" 입니다.
Promise
프로미스는 비동기 처리를 위해 만들어진 객체입니다.
보통 아래와 같은 형식을 통해 프로미스 객체를 생성합니다.
- 프로미스 객체는 new 키워드로 만든다.
- 생성자 함수 Promise의 괄호 안에 executor라는 함수를 하나의 인수로 받는다
executor 함수 : ( (resolve,reject) => { } ) **** 이 덩어리 자체가 함수 executor입니다.
- executor 함수 자체의 return값은 없다.
- executor 함수는 매개변수로 resolve(), reject() 두 가지 함수를 갖는다.
- 이 두 함수는 매개변수로 넘겨지고 있으므로 callback 함수다. ( 나증에 다시 호출될 것이다.)
- executor 함수에서는 비동기 작업이 실행된다.
- 비동기 작업이 성공하면 resolve()함수가 호출되고, 작업이 실패하면 reject()함수가 호출된다.
* 프로미스 객체는 두 가지 속성이 있다. ✌🏻
state(상태) , result(결괏값)
1. state : 대기(pending) or 이행(fulfilled) or 실패(rejected)
2. result : resolve나 reject에 인수로 전달된 value
따라서 우리는 프로미스 객체의 상태와 결과값을 통해 비동기 작업의 진행 상황과 결과를 추적할 수 있다.
- 비동기 작업이 성공하느냐 실패하느냐에 따라 서로 다른 후처리가 요구된다.
- 후처리는 각각 then메소드와 catch메소드에 의해 이루어진다.
- 두 메소드는 프로미스 객체에 내장되어 있다.
then 메소드 : 해당 프로미스가 성공했을 때의 동작을 지정. 인수로 함수를 받음.
catch 메소드 : 해당 프로미스가 실패했을 때의 동작을 지정. 인수로 함수를 받음.
--> 인자로 받은 함수가 특정 동작을 실행하는 역할을 하므로 여기서의 인자로 받은 함수를 핸들러라고 함. *
+ finally 메소드 : 해당 프로미스가 성공했던 실패했던 무조건 실행되는 동작을 지정. 인수로 함수를 받음.
아래 코드를 보시면 새로운 프로미스 객체 mimi의 생성 코드, 프로미스 mimi의 executor함수에는 매개변수로 resolve함수와 reject함수가 넘겨지고 있으며, 함수 내부에는 setTimeout함수에 의해 비동기적인 작업이 일어나는 코드가 보이고, 이 비동기적인 작업의 성공과 실패에 따른 적합한 후처리에 사용될 then 메소드와 catch 메소드 코드 등이 보여지고 있습니다.
(3초 후 이 세 문장이 동시에 출력될 것. )
- 시작
- 작업 중 오류가 발생했습니다.
- 끝
* 작업 결과값을 전달할 수 있다 !
앞에서 배운 resolve와 reject 함수에 인자를 전달하면 then과 catch함수는 비동기작업에 대한 정보를 얻게 된다.
resolve나 reject에 전달되는 인자(=프로미스 객체의 result 속성값)는 then과 catch 내에 있는 핸들러의 value값으로 들어가게 된다는 뜻이다.
- 출력 결과
(3초 후)
성공했습니다.
마침.
프로미스 객체 안에서 resolve함수를 실행시켰다는 것은 프로미스 객체 속 비동기 작업이 성공했다는 의미이므로 3초 후에 외부에서는 then메소드가 실행될 것이다. 그러니까 3초가 지난 후 resolve 함수 괄호 속에 든 인자 "성공했습니다."가 then메소드의 value값으로 들어가게 되므로 결과적으로는 프로미스 결괏값(result)이 출력된다.
+ finally는 성공/실패의 유무와 관계없이 늘 실행되므로 "마침."도 함께 출력된다.
* 프로미스 체이닝 ⛓️
- then 메소드가 체인 형식으로 연결되어 사용되는 형태를 말함. 아래의 특징 덕분에 가능.
then 메소드를 호출하면 (then메소드에 return값이 있을 경우) 새로운 프로미스 객체를 반환한다. ******
( 사실 then, catch메소드 모두 프로미스 객체를 반환함. 이를 암기하기보다 그냥 이렇기 때문에 체이닝이 가능하다고 알고 유용하게 이용하면 됨. )
이 특징을 이용하여 여러 개의 프로미스를 연결하여 사용할 수 있다. ( = 프로미스 체이닝 )
프로미스 체이닝을 통해, 앞서 살펴봤던 callback 지옥 코드를 훨씬 가독성 있게 표현해낼 수 있게 된다.
- 출력값
(3초 후)
1
2
6
* 프로미스를 재사용 해 보자 ! 🔄
( 특정 함수를 호출할 때마다 새로운 프로미스가 반환된다면?!)
- 출력값
"진표은(는) 성인입니다."
"미성은(는) 성인이 아닙니다."
함수 fn을 호출할 때마다 fn의 매개변수 name과 age를 반영하며 매번 새로운 프로미스 객체가 반환된다.
따라서 함수 fn을 이용하여 pyo 프로미스와 mimi 프로미스를 생성한 후, 각 프로미스에서 then메소드와 catch메소드를 호출해 보았다.
개인적으로 어려운 만큼 재미있기도 한 프로미스 파트였습니다. 다른 파트들보다 이해하고 받아들이는 데에 시간이 오래 걸렸던 것 같아요. 저는 현재 자바스크립트 강의를 들으며 배운 내용을 이렇게 블로그로 포스팅하며 복기하고 있는데요, 강의가 어느덧 막바지를 향해 달려가고 있습니다! ⭐️🌟
( 강의 중반부터 글 작성을 시작해 아직 못 올라간 포스팅들이 많습니다. 🥹 계속해서 업로드 할 예정입니다! )
🔥 끝까지 파이팅 하겠습니다! 🔥
읽어주셔서 감사합니다. 오늘도 좋은 하루 보내세요 !
참고
[Javascript 비동기 처리 뿌시기 (3/4)] 비동기 처리 방식 : 자바스크립트 Promise 완전 쉽게 이해하기 (프라미스, 프라미스 체이닝) (tistory.com)
인프런 코딩앙마 님 javascript 중급 강의
'javascript' 카테고리의 다른 글
[javascript] 유용한 객체 메소드 / 숫자 메소드 총정리 (5) | 2024.03.13 |
---|---|
[javascript] 클로저(closure) (3) | 2024.03.11 |
[javascript] 클래스(class)란 무엇인가? (31) | 2024.03.04 |
[javascript] 상속, 프로토타입(prototype) (30) | 2024.03.03 |
[javascript] 유용한 배열 메소드 (array method) 총정리 (25) | 2024.03.03 |