본문 바로가기

JS/JavaScript

20. 동기와 비동기, Promise, async&await

동기와 비동기?

  (Thread) 작업1 → 작업2 → 작업3   

 

위와 같은 작업이 있을 때 순서대로 작업이 처리되는 것을 동기라고 하며 앞의 작업이 진행 중일 때는 뒤의 작업을 수행할 수 없다. 

JavaScript에서 기본적으로 동작하는 것은 동기이다. 이는 직관적이고 순차적이기 때문에 코드의 이해와 디버깅이 상대적으로 쉽다. 

하지만, 앞 작업이 길어질 경우 뒷 작업도 늦어지기 때문에 브라우저나 앱의 응답 시간이 길어지는 단점이 있다. 

 

이를 해결하기 위해 JavaScript에는 비동기 패턴을 제공한다. 비동기 패턴은 콜백함수, Promise, async/await 등을 사용하여 처리하며 작업이 완료되면 특정 콜백함수를 전달한다면 해당 작업이 끝났음을 알 수 있다. 비동기 패턴을 이용하면 시간이 오래 걸리는 작업이 진행되는 동안 다른 작업을 수행할 수 있다. JavaScript에서는 주로 이를 이용하여 네트워크 요청, 파일 업로드, 타이머 등과 같은 작업을 처리한다. 

function firstTask() {
  setTimeout(() => {
    console.log("첫 번째 작업");
  }, 2000);
}
// setTimeout : 비동기 함수(실행할 코드, 시간) * 시간은 1000 = 1초

function secondTask() {
  console.log("두 번째 작업");
}

firstTask();
secondTask();

 

콜백 함수 사용

function fristTask(a, callback) {
  setTimeout(() => {
    const result = a + "첫 번째 작업";
    callback(result);
  }, 4000);
}

function secondTask(a, callback) {
  setTimeout(() => {
    const result = a + "두 번째 작업";
    callback(result);
  }, 3000);
}

function thirdTask(a, callback) {
  setTimeout(() => {
    const result = a + "세 번째 작업";
    callback(result);
  }, 2000);
}

console.log("코드 시작");

fristTask("1. ", (result) => {
  console.log(result);
});

secondTask("2. ", (result) => {
  console.log(result);
});

thirdTask("3. ", (result) => {
  console.log(result);
});

 


 

Promise

JavaScript에서 비동기 작업을 처리하기 위한 내장 객체로 , 비동기 작업의 성공 또는 실패를 나타내고 그 결과값을 나중에 받을 수 있는 방법을 제공한다. 

 

Promise 객체의 상태

1. 대기(Pending): 초기상태, 비동기 작업의 완료 또는 실패를 대기한다. 

2. 이행(Fulfilled): 비동기 작업 성공. Promise 객체는 완료된 값으로 이행된다. 

3. 거부(Reject): 비동기 작업 실패. Promise 객체는 실패한 이유를 나타내는 오류로 거부된다. 

 

Promise 객체 상태변화 처리를 위한 메서드

1. then(이행callback, 거부callback): Promise 객체가 이행됐을 때와 거부됐을 때 호출되는 콜백함수 등록

2. catch(거부callback): Promise 객체가 거부됐을 떄 호출되는 콜백함수 등록

3. finally(항상호출되는callback): Promise 객체가 이행되거나 거부된 후에 호출되는 콜백함수 등록. 

 

const promiseEx = new Promise((success, reject) => {
  // 상수 promiseEx를 선언하고 생성자 함수 new Promise()를 호출하여 Promise 객체 생성
  // Promise 객체 안에 완료되었을 때 콜백함수(success), 거부되었을 때 콜백함수(reject) 전달

  // 비동기 작업 수행
  const isSuccess = false; //   비동기 작업 결과

  if (isSuccess) {
    success("success 콜백함수 호출"); // 작업이 성공했을 경우 resolve 호출
  } else {
    reject("reject 콜백함수 호출"); // 작업이 실패했을 경우 reject 호출
  }
});

promiseEx
  .then((result) => {
    console.log("Promise 성공:", result);
  })
  .catch((error) => {
    console.log("Promise 실패:", error);
  })
  .finally(() => {
    console.log("...Promise 완료");
  });

 


 

async & await

JavaScript에서 비동기 코드를 보다 간결하고 동기적으로 작성할 수 있게 해주는 문법적 요소 

 

1. async 

- async 키워드를 사용하여 함수를 정의하면 해당 함수는 비동기 함수로 선언된다.

- async 함수 안에는 await 키워드를 사용하여 비동기 작업의 결과를 기다릴 수 있다.

- 항상 Promise를 반환하며 내부에 명시적으로 return문을 사용하여 값을 반환하면 해당 값으로 이행된 Promise가 반환된다. 

 

2. await

- await 키워드는 async 함수 내에서만 사용할 수 있으며 Promise가 이행될 때(비동기 작업이 완료될 때)까지 함수의 실행이 일시중단.

- 오직 Promise 객체 앞에만 사용할 수 있으며 해당 Promise가 이행되면 그 결과를 반환한다. 

 

function taskA(a, b, callback) {
  setTimeout(() => {
    const res = a + b;
    callback(res);
  }, 3000);
}

function taskB(a, b, callback) {
  setTimeout(() => {
    const res = (a + b) * -1;
    callback(res);
  }, 1000);
}

function runTasks() {
  try {
    const resultA = taskA(10, 10, (res) => {
      console.log("resultA: ", resultA);
    });

    const resultB = taskB(10, 10, (res) => {
      console.log("resultB: ", resultB);
    });
  } catch (error) {
    console.error(error);
  }
}

runTasks();

마지막 줄의 runTasks() 함수를 실행한 시점엔 taskA와 taskB가 완료되지 않았기 때문에(setTimeout) 결과가 모두 undefined가 된다.

이를 해결하기 위해 async와 await를 사용하는 것이다. 

function taskA(a, b) {
  return new Promise((success) => {
    setTimeout(() => {
      const res = a + b;
      success(res);
    }, 3000);
  });
}

function taskB(a, b) {
  return new Promise((success) => {
    setTimeout(() => {
      const res = (a + b) * -1;
      success(res);
    }, 1000);
  });
}

async function runTasks() {
  try {
    const resultA = await taskA(10, 10);
    console.log("resultA: ", resultA);

    const resultB = await taskB(10, 10);
    console.log("resultB: ", resultB);
  } catch (error) {
    console.error(error);
  }
}

runTasks();

taskB의 setTimeout이 1초이고 taskA가 3초로 taskB가 먼저 수행이 되는 데도 결과는 A->B 로 나오는 이유는 await는 비동기 작업이 완료될 때까지 다음작업이 수행되지 않기 때문이다.