동기와 비동기의 차이점에 대하여 알아보자

동기(Synchronous)와 비동기(Asynchronous)

동기는 직렬적으로 작동하는 방식이고, 비동기는 병렬적으로 작동하는 방식이다. 동기는 특정 코드가 끝나기 전에 다음 코드가 실행되지 않지만, 비동기는 코드가 끝날때 까지 실행을 멈추는 것이 아니라 바로 다음 코드를 실행한다.

동기적 처리 (synchronous)

  • 요청을 보낸 후 응답을 받아야지만 다음 동작이 이루어지는 방식. 어떤 테스크를 처리하고 있을 때 나머지 테스크는 대기한다.
  • 시스템의 전체적인 효율이 저하된다.

비동기적 처리 (asynchronous)

  • 요청을 보낸 후 응답에 상관 없이 다음 동작이 이루어지는 방식. 여러 테스크가 동시에 처리되므로 자원을 효율적으로 사용하는 것이 가능하다.
  • 어떤 테스크가 완료 되었을 때 일반적으로 callback 함수가 호출된다.

비동기 처리를 위해 callback 패턴을 사용하면, 처리 순서를 보장하기 위해 여러 callback 함수가 중첩되어 복잡도가 높아지는 콜백 지옥(callback hell)이 발생하는 단점이 있다. 이러한 콜백 지옥은 가독성을 나쁘게 하며 개발자로 하여금 실수를 유발하는 원인이 된다.


Promise란 무엇인가?

자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 이러한 콜백 패턴은 콜백 지옥이 발생한다는 한계를 가지고 있다. 따라서 ES6에서는 비동기 처리를 위한 또 다른 패턴으로 프로미스(Promise)를 도입했다.

const promise = new Promise((resolve, reject) => {
    // 비동기 처리
    if (/* success */){
        resolve('result');
    } else { /* fail */
        reject('failure reason');
    }
})

프로미스는 Promise 생성자 함수를 통해 인스턴스화한다. Promise 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인자로 전달받는다.

프로미스 객체는 비동기 처리의 성공과 실패 여부에 따라 4가지의 상태 정보를 갖는다.

  • pending : 비동기 처리가 아직 수행되지 않은 상태
  • fulfilled : 비동기 처리를 성공한 상태
  • rejected : 비동기 처리를 실패한 상태
  • settled : 비동기 처리가 수행된 상태 (성공 / 실패)

비동기 함수 내에서 Promise 객체를 생성하고, 그 내부에서 비동기 처리한다. 비동기 처리에 성공하면, resolve 메소드를 호출한다. 이때 resolve 메소드의 인자로 비동기 처리 결과를 전달 하는데, 이 처리 결과는 Promise의 후속 처리 메소드 then 으로 전달된다. 만약 비동기 처리에 실패하면 reject 메소드를 호출한다. 이때 reject 메소드의 인자로 에러 메시지를 전달한다. 이 에러 메시지는 Promise 객체의 후속 처리 메소드 catch 로 전달된다.

다음 예제를 확인해보자.

const ifZeroResolve = () => {
    return new Promise((resolve, reject) => {
        const n = Math.floor(Math.random()*2);
        console.log("the n is ... " + n);
        if(n === 0){
            resolve('success, n is 0');
        }else{
            reject('failed, n is 1, not 0');
        }
    })
}
ifZeroResolve()
    .then((data)=>{ // resolve
        console.log(data);
    })
    .catch((err)=>{ // reject
        console.log(err);
    })
  • n 의 값이 0인 경우 resolve 메소드가 호출되는데, 이때 인자로 success, n is 0 이 전달된다.
  • n 의 값이 1인 경우 reject 메소드가 호출되는데, 이때 인자로 failed, n is 1, not 0 이 전달된다.

Promise chaining 비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 콜백 지옥이 발생하는데, Promise는 후속 처리 메소드인 then 이나 catch 로 메소드를 체이닝하여 여러 개의 Promise를 연결하여 사용하는 방식을 통해 콜백 지옥을 해결한다.


Async / Await

asyncawait은 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법으로 callback 함수와 Promise의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다. async, await의 기본 문법은 다음과 같다.

const funcA = async () => {
    await funcB();
}

arrow function에서 async 키워드는 () 앞에 붙는다. async를 붙이면 해당 함수는 항상 Promise를 반환한다. Promise가 아닌 값을 return 하더라도 이행 상태의 Promise (resolve promise)로 값을 감싸 이행된 Promise가 반환되도록 한다. 다음 예시를 확인해 보자.

const funcC = async () => {
    return 1;
    //return Promise.resolve(1);
}
funcC()
    .then((result) => {
        console.log(result);
    })

async 키워드가 붙어 있으므로 funcC 는 resolve Promise를 return 한다. 따라서 Promise 후속 처리 메소드인 then 이 실행되고 결과적으로 1 이 출력된다.

await은 async 함수 안에서만 동작하는데, Promise가 처리될 때까지 기다리는 역할을 한다. 다음 예시를 확인해 보자.

const funcD = async () => {
    const promise = new Promise((resolve, reject) => {
        setTimeOut(() => resolve('success'), 1000)
    });

    const result = await promise;

    console.log(result);
}
funcD();

함수를 호출하고, 함수 본문이 실행되는 도중에 await을 만나면, 해당 줄에서 실행이 중단되었다가 Promise가 처리되면 실행이 재개된다. 이때 Promise 객체의 결과 값이 변수 result 에 할당된다. 따라서 위 예시를 실행하면 1초 뒤에 success 가 출력된다. 만약 await를 사용하지 않았다면 데이터를 받아온 시점에 Promise 후속 처리 메소드인 then 을 사용해야 했을 것이다. 하지만 await를 이용한다면 비동기 처리를 신경쓰지 않고 Promise의 결과 값을 얻어낼 수 있다.

await을 이용할 때, 에러가 발생할 수 있는데, 이는 try, catch 를 이용해 처리할 수 있다. 다음 예시를 확인해보자.

const funcE = async () => {
    try {
        const result = await fetch('invald url')
    } catch (error) {
        console.log(error)
    }
}
funcE();

댓글남기기