[프론트엔드 기술면접] 비동기란 무엇일까? (feat. promise, async/await)

LIMHALIM·2023년 11월 20일
1

비동기란?

자바스크립트에서 비동기 처리란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것을 의미합니다. 비동기 처리를 예로 Ajax, setTimeout 등이 있습니다. 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있지요!
자바스크립트는 비동기 처리를 위해 콜백함수를 사용합니다. 하지만 콜백을 너무 남용하게 되면 우리가 흔히 부르는 콜백 지옥에 빠질 수가 있습니다.
또한, 에러처리도 힘들 뿐더러 여러 개의 비동기 처리를 한번에 하는데 한계가 있습니다.
이런 콜백 함수의 단점을 보완하며 비동기 처리에 사용되는 객체를 프로미스(Promise)라 합니다.

Promise란?

프로미스는 객체이기 때문에 생성자 함수를 호출하여 인스턴스화할 수 있습니다.
프로미스의 생성자 함수는 resovle와 reject 함수를 인자로 전달받는 콜백함수를 인자로 전달받습니다.
프로미스는 인자로 전달받은 콜백 함수를 내부에서 비동기 처리 합니다.
Promise는 비동기 처리가 성공(fulfilled)하였는지 또는 실패(rejected)하였는지 등의 상태(state) 정보를 갖게 됩니다.
resolve 함수가 호출 된 경우 성공된 상태이고, reject 함수가 호출 된 경우 실패 상태이게 됩니다.

후속 처리 메소드

프로미스로 구현된 비동기 함수를 호출하는 측에서는 프로미스 객체의 후속 처리 메소드(then, catch)를 통해 비동기 처리 결과 또는 에러 메세지를 전달받아 처리합니다.

  • then
    then 메소드는 두 개의 콜백 함수를 인자로 전달 받습니다.
    첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 경우)시에 실행됩니다.
    두 번째 콜백 함수는 실패(rejected, reject 함수가 호출된 경우)시에 실행됩니다.
    then 메소드는 기본적으로 프로미스를 반환합니다.

  • catch
    catch 메소드는 비동기 처리 혹은 then 메소드 실행 중 발생한 에러(예외)가 발생하면 호출됩니다.
    catch 메소드 역시 프로미스를 반환합니다.


프로미스는 정적 메소드 5가지를 제공하는데요, 실제 프로젝트에서 사용했던 Promise.allPromise.allSettled를 소개해보고 그때 제가 고민했던 것을 공유해보겠습니다!

먼저 Promise.all이란 프로미스가 담겨있는 배열과 같은 이터러블 객체를 인자로 받습니다.
인자로 전달받은 모든 프로미스를 병렬로 처리하고 그 결과값을 배열에 담아 resolve로 반환합니다. 가장 마지막으로 끝나는 프로미스를 기준으로 수행되고, 모든 프로미스가 fullfilled 상태가 되면 결과값을 배열에 담아 새로운 프로미스를 반환합니다.
프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료합니다.

그럼 Promise.allSettled는 무엇일까요?
Promise.allSettled 메소드 역시 Promise.all 메소드와 동일하게 프로미스가 담겨있는 이터러블 객체를 인자로 받고 병렬로 처리합니다.
다만, Promise.all 의 경우 프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료하게되지만, Promise.allSettled 메소드의 경우 rejected 상태가 되어도 수행을 종료하지않고, 프로미스가 수행된 상태와 결과값을 배열에 담아 resolve로 반환합니다. Promise.all 메소드와 또다른 차이점은 각각의 프로미스 처리결과를 객체로 나타내고 status 프로퍼티를 가지게됩니다.

[
  { status: 'fulfilled', value: 1 },
  { status: 'rejected', reason: 2 }
]

fullfilled 상태인 경우 value 프로퍼티를 가지게되고, rejected 상태인 경우 reason 프로퍼티를 가지게됩니다.

💛 정리해보자면!
Promise.all과 Promise.allSettled 둘다 병렬적으로 실행시키기 위한 메소드로 동일한 점이 있지만, all의 경우 하나라도 reject가 되면 모든 promise들이 catch로 빠지게 되면서 성공한 promise들도 무시돼서 다시 보내야하는 번거로움이 있는데, allSettled의 경우 각 promise에 대한 처리 상태와 결과 값을 배열에 같이 보내져서, rejected된 promise에 대해서 분기 처리가 가능하다고 합니다 !!!

프로젝트 당시에 게시글 목록 중 여러개를 선택해서 한개씩 삭제 API를 호출해야 하는 로직이 있었는데 전 그때 아무것도 모르고 map을.. 써서 처리를 했다는,, ㅋㅋㅋ 🤣
팀원분들이 위 두개의 메소드를 알려주셔서 잘 처리했답니당. all을 써야할지 allSetteld 써야할지 고민했었는데 백엔드 팀원분과 상의를 하거나 기능 목적에 따라 달라지게 될 것 같아요!

const handleDelete = async () => {
  try {
    await Promise.all(
      deleteCheckedItems.map(boardId =>
        API.delete('/api/boards/my', {
        params: {
        boardId: boardId,
        },
        })
      )
    );
    setMyPostListCount(prev => prev - deleteCheckedItems.length);
    handleToggle();
  } catch (error) {
    alert('게시글 삭제 실패');
    console.error(error);
  }
};

async/await?

async/await 문법은 ES8에 해당하는 문법으로서, Promise 를 더욱 쉽게 사용 할 수 있게 해줍니다.
함수를 선언 할 때 함수의 앞부분에 async 키워드를 붙여주세요. 그리고 Promise 의 앞부분에 await 을 넣어주면 해당 프로미스가 끝날때까지 기다렸다가 다음 작업을 수행 할 수 있습니다.

Promise 코드

function p() {
return new Promise((resolve, reject) => {
	resolve('hello');
// or reject(new Error('error');
	});
}

p().then((n) => console.log(n));

async 코드

async function p2(){ // async을 지정해주면 Promise를 리턴하는 함수로 만들어줍니다.
	return 'hello'; 
}

p2().then((n) => console.log(n));

이처럼 async를 사용하면 promise 코드를 훨씬 직관적으로 나타낼 수 있습니다.
함수에 async만 붙이면 자동으로 promise 객체로 인식되고, return 값은 resolve() 값과 동일합니다.

async function에서 어떤 값을 리턴하든 무조건 프로미스 객체로 감싸져 반환 된다는 특징이 잇습니다.

  • async
    async 키워드는 어렵게 생각할 필요없이 await를 사용하기 위한 선언문 정도로 이해하면 된다고 합니다. 즉, function 앞에 async을 붙여줌으로써, 함수내에서 await 키워드를 사용할 수 있게 되는거죠. 이는 반대로 말하면 await 키워드를 사용하기 위해선 반드시 async function 정의가 되어 있어야 한다는 말과 같습니다.
  • await
    자바스크립트는 await 키워드를 만나면 프라미스가 처리될 때까지 기다립니다

예외 처리

프로미스에서 예외처리를 할 때 후속 처리 메소드인 catch() 메소드를 사용하여 예외처리를 하였습니다.

이제 async와 await을 사용하면 프로미스를 함수 내부에서 동기적으로 처리할 수 있기 때문에 try-catch 구문을 사용 예외 처리를 할 수 있습니다.

물론 모든 예외를 try-catch 구문으로 처리하는 것은 아닙니다. await은 async가 붙은 함수 내에서만 사용 가능하기 때문에 최종결과나 처리되지 못한 에러의 경우 catch() 메소드를 사용해 처리해 주곤 합니다.

async function promise() {
    throw 'rejected';
}

async function exceptionFunc() {
    try {
        await promise()
    } catch (e) {
        console.log('catch error!', e)
    }
}

exceptionFunc()
profile
모든 익숙함에 물음표 더하기

0개의 댓글