-
비동기 함수를 동기로 다루기 callback, promise, async-awaitFrontend/Javascript 2023. 3. 14. 22:28
동기와 비동기 개념 차이
동기와 비동기 개념의 차이에 대해서는 이전에 포스팅한 적이 있다.
가볍게 정리하면 동기는 A, B, C의 호출이 순차적으로 있었을 때 A가 마무리 되고 나서야 B가 실행되고
B가 실행된 이후에야 C가 실행될 수 있다. 즉 순서가 순차적이다.
반면에 비동기함수는 A, B, C 호출이 동시에 일어난다.
특정 코드의 연산이 끝날 때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행한다.
A, B, C를 동시에 실행했을 때 B와 C의 결과가 먼저 발생될 수도 있다.
반면 웹개발을 하다보면 비동기 함수를 동기처럼 쓰고싶을 때가 있다.
함수를 호출하자마자 즉각적으로 실행되길 원하지만, 호출 후 데이터를 받고나서 이후의 작업이 필요할 때 주로 그러한 일들이 많았다.
주로 API를 호출하여 받은 결과물을 클라이언트사이드의 store나 state에 저장하고 싶을 때 그런 니즈가 많이 발생한다.
이렇게, 비동기를 동기로 다루는 방법을 크게 3가지로 소개한다.
1. Callback 함수
콜백함수란 함수에 매개변수로 함수를 전달받아 실행하는 함수이다.
대표적인 비동기 함수로는 setTimeout이라는 함수가 있다. 이는 특정 시간이 넘으면 넘겨 받은 콜백함수가 실행되는 구조이다.
function async(callback) { setTimeout(() => { callback("1초 후 실행"); }, 1000); } async(function (msg) { console.log(msg); });
위 함수를 실행하면 정말로 1초 뒤에 callback함수가 실행되어 "1초 후 실행" 이라는 콘솔이 찍히게 된다.
위와 같은 방법은 콜백을 한 번만 실행시켰을 때는 가독성에도 문제가 크지 않지만, callback() 함수를 실행한 그 이후에 callback() 함수의 결과물을 받아 다른 함수가 실행되어야 될 때 아래와 같이 작성하게 된다.
function async(result, callback) { setTimeout(() => { callback(result, function (result) { console.log(result); }); }, 1000); } async(0, function (res, callback) { callback(res) async(res + 1, function (res, callback) { callback(res) async(res + 1, function (res, callback) { callback(res) }); }); });
딱봐도 가독성이나 코드의 흐름을 읽기 어려워지고, 작성하기도 까다로워진다.
그래서 Promise를 사용하게된다.
2. Promise
우선, Promise 함수의 경우 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖는 객체이다.
기본적으로 Promise의 경우 비동기 처리가 성공하면 resolve를 뱉고, .then 키워드로 res를 받을 수 있다.
만약 비동기 처리가 실패하면 reject를 뱉고 catch 키워드로 error message를 받을 수 있다.
function async(key) { return new Promise((resolve, reject) => { setTimeout(() => { if (key === true) resolve("waited 1 sec."); else reject(new Error("Error!")) }, 1000); }) } async(true) .then((result) => { console.log(result); }) .catch((error) => { console.log(error); });
위에서 확인할 수 있 듯, 함수를 한번 실행하면.then()과 .catch() 키워드로 결과값을 받아 다음으로 실행될 함수를 작성할 수 있다.
조금 더 복잡하게 함수를 실행시킨다고 하여도 콜백함수로 작성하였을 때보다 코드가 간결해진다.
const result = Promise1() //return 값과 해당 함수 파라미터가 같은 경우 생략가능 .then(Promise2).catch(console.log) .then(Promise3).catch(console.log) .then(console.log);
Promise의 경우 Promise.all 이라는 메소드도 제공하는 이는 모든 비동기처리가 완료가 되어야지 다음을 실행할 수 있는 메소드이다.
const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); });
주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환한다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부한다.
3. Async, Await
개인적으로 가장 직관적이고, 가장 가독성이 좋다고 생각하는 방법입니다.
const fetchComment = async () => { try { const { comments } = await getComments(URLSlug!); setComments(comments); } catch (err) { console.log(err); } };
callback 함수 사용에 비해 위에서부터 아래로 코드를 읽어나가기 편하고 await 이후의 함수의 결과값을 기다렸다가 반환된 값을 이용하여 다음 함수에 전달하는 방식입니다.
function 앞에 async 키워드를 추가하면 두 가지 효과가 있습니다.
- 함수는 언제나 promise를 반환한다.
- 함수 안에서 await를 사용할 수 있다.
promise 앞에 await 키워드를 붙이면 자바스크립트는 promise가 처리될 때까지 대기합니다.
처리가 완료되면 조건에 따라 아래와 같은 동작이 이어진다.
- 에러 발생 – 예외가 생성됨(에러가 발생한 장소에서 throw error를 호출한 것과 동일함)
- 에러 미발생 – promise 객체의 result 값을 반환
async/await를 함께 사용하면 읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.
async/await를 사용하면 promise.then/catch가 거의 필요 없게 된다.
하지만 가끔 가장 바깥 스코프에서 비동기 처리가 필요할 때 같이 promise.then/catch를 써야만 하는 경우가 생기기 때문에 async/await가 promise를 기반으로 한다는 사실을 알고 있어야 한다.
'Frontend > Javascript' 카테고리의 다른 글
javascript 클로저 예제 및 쓰는 이유 (0) 2023.04.06 javascript 함수 호출 방식에 따른 this 동적 바인딩 (0) 2023.04.04 렉시컬 환경, 실행 컨텍스트, 스코프 (0) 2023.04.03 자바스크립트 함수, 일급 객체, 함수 생성 시점과 함수 호이스팅 (0) 2023.04.03 CORS 이해하기 및 해결방법 (0) 2022.12.21