[개발] JavaScript 비동기 프로그래밍: async/await

비동기 프로그래밍의 필요성

비동기 프로그래밍은 실제 개발에서 매우 중요한 개념입니다. 일반적으로 자바스크립트에서는 이벤트 처리, 네트워크 요청, 파일 입출력 등과 같은 작업을 수행할 때 비동기 방식을 사용합니다. 비동기 프로그래밍은 작업을 순차적으로 실행하는 동기적인 방식과는 달리, 여러 작업을 병렬적으로 실행하여 성능과 사용자 경험을 향상시킬 수 있습니다.

비동기 방식의 동작 원리

비동기 방식은 작업을 백그라운드 스레드에서 실행하고, 작업이 완료되면 결과를 콜백 함수를 통해 전달합니다. 이를 통해 메인 스레드는 작업이 완료될 때까지 대기하지 않고 다른 작업을 처리할 수 있습니다.

코드 예시: setTimeout 함수


console.log("Start");

setTimeout(function() {
   console.log("Async Task Done");
}, 2000);

console.log("End");

콜백 함수

콜백 함수는 비동기 프로그래밍에서 많이 사용되는 개념입니다. 콜백 함수는 다른 함수에게 인자로 전달되어 특정 이벤트나 작업의 결과를 처리하는 역할을 합니다. 비동기 작업이 완료되면 콜백 함수가 호출되어 그 결과를 받아 처리합니다.

콜백 함수의 사용 예시


function fetchData(callback) {
   setTimeout(function() {
      const data = "This is the fetched data";
      callback(data);
   }, 2000);
}

function processData(data) {
   console.log("Processing data: " + data);
}

fetchData(processData);
console.log("End");

위의 예시에서 fetchData 함수는 비동기로 데이터를 가져오는 역할을 합니다. fetchData 함수의 인자로 processData 함수가 전달되어, 데이터가 성공적으로 가져와지면 processData 함수가 호출됩니다. 따라서 “Processing data: This is the fetched data”가 출력됩니다.


Promise 객체

Promise 객체는 비동기 작업을 더 효율적으로 다룰 수 있는 방법을 제공하는 자바스크립트의 내장 객체입니다. Promise 객체는 작업의 성공, 실패, 진행 상태 등 다양한 상태를 추적하고 관리할 수 있습니다.

Promise 객체의 주요 메서드

  • then: Promise 객체가 성공 상태일 때 작업을 처리하는 콜백 함수를 등록합니다.
  • catch: Promise 객체가 실패 상태일 때 예외를 처리하는 콜백 함수를 등록합니다.
  • finally: Promise 객체의 작업이 성공 또는 실패하더라도 마지막에 반드시 실행되는 콜백 함수를 등록합니다.

Promise 객체의 사용 예시


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

fetchData()
   .then(function(data) {
      console.log("Data fetched successfully: " + data);
   })
   .catch(function(error) {
      console.log("Failed to fetch data: " + error.message);
   })
   .finally(function() {
      console.log("Data fetching process completed");
   });

console.log("End");

위의 예시에서 fetchData 함수는 Promise 객체를 반환합니다. Promise 객체는 비동기 작업이 완료되면 해당 작업의 결과 값을 resolve 메서드를 통해 전달합니다. 만약 작업이 실패하면 reject 메서드를 사용하여 예외를 발생시킵니다. then, catch, finally 메서드를 이용하여 Promise 객체의 상태를 추적하고 작업의 성공, 실패 및 완료 시 적절한 작업을 수행할 수 있습니다.


async/await 문법

async/await 문법은 Promise 객체를 더 직관적이고 동기적으로 다룰 수 있는 방법을 제공하는 자바스크립트의 문법입니다. async 함수는 항상 Promise 객체를 반환하며, await 키워드는 async 함수 내에서만 사용할 수 있습니다. await 키워드를 사용하면 Promise가 완료될 때까지 async 함수의 실행이 일시 중지되고, Promise가 완료되면 해당 결과 값을 반환합니다.

async/await의 사용 예시


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

async function processAsync() {
   try {
      const data = await fetchData();
      console.log("Data fetched successfully: " + data);
   } catch (error) {
      console.log("Failed to fetch data: " + error.message);
   } finally {
      console.log("Data fetching process completed");
   }
}

processAsync();
console.log("End");

위의 예시에서 processAsync 함수는 async 함수로 정의되어 있습니다. async 함수 내에서 fetchData 함수를 호출하고, 그 결과를 await 키워드를 통해 받아 처리합니다. await 키워드를 만나면 async 함수의 실행이 일시 중지되며, Promise가 완료될 때까지 기다립니다. 만약 작업이 성공하면 해당 결과 값을 받아와서 처리하고, 실패하면 catch 블록으로 이동하여 예외를 처리합니다. 마지막으로 finally 블록에서 작업의 완료를 나타낼 수 있습니다.


async/await 사용 예시

async/await 문법을 사용하면 비동기 작업을 더 직관적으로 다룰 수 있습니다. 아래는 async/await 문법을 사용하여 비동기 작업을 처리하는 예시입니다.

Promise를 사용한 비동기 함수


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

function processPromise() {
   fetchData()
      .then(function(data) {
         console.log("Data fetched successfully: " + data);
      })
      .catch(function(error) {
         console.log("Failed to fetch data: " + error.message);
      })
      .finally(function() {
         console.log("Data fetching process completed");
      });
}

processPromise();
console.log("End");

async/await를 사용한 비동기 함수


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

async function processAsync() {
   try {
      const data = await fetchData();
      console.log("Data fetched successfully: " + data);
   } catch (error) {
      console.log("Failed to fetch data: " + error.message);
   } finally {
      console.log("Data fetching process completed");
   }
}

processAsync();
console.log("End");

위의 예시에서는 fetchData 함수가 Promise를 반환하는 비동기 함수입니다. processPromise 함수는 then, catch, finally 메서드를 사용하여 Promise 객체의 상태를 처리하고, processAsync 함수는 async/await 문법을 사용하여 Promise 객체를 동기적으로 처리합니다. 두 가지 방식 모두 비동기 작업에서 데이터를 가져오고, 작업의 성공과 실패를 처리하며, 작업의 완료를 알리는 로그를 출력합니다. 하지만 async/await 문법을 사용한 코드는 더 직관적이고 읽기 쉬운 구조를 가지고 있습니다.


Promise와 async/await의 차이점

Promise와 async/await은 모두 비동기 작업을 다루는 데 사용되지만, 사용 방법과 처리 방식에 차이가 있습니다.

Promise


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

fetchData()
   .then(function(data) {
      console.log("Data fetched successfully: " + data);
   })
   .catch(function(error) {
      console.log("Failed to fetch data: " + error.message);
   });
console.log("End");

async/await


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
         // reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

async function processAsync() {
   try {
      const data = await fetchData();
      console.log("Data fetched successfully: " + data);
   } catch (error) {
      console.log("Failed to fetch data: " + error.message);
   }
}

processAsync();
console.log("End");

위의 예시에서 Promise와 async/await을 사용하여 비동기 작업을 처리하는 방법을 비교해보겠습니다. Promise는 .then()과 .catch() 메서드를 사용하여 비동기 작업을 처리합니다. .then()을 사용하여 작업이 성공하면 결과 값을 받아와 처리하고, .catch()를 사용하여 작업이 실패하면 에러를 처리합니다. 순차적으로 작업을 체이닝할 수 있으며, 작업이 완료된 후에 .finally()를 사용하여 완료를 알릴 수 있습니다.

반면에 async/await은 async 함수 내에서 await 키워드를 사용하여 비동기 작업의 완료를 기다립니다. 작업의 성공은 try 블록 내부에서 처리되고, 작업의 실패는 catch 블록에서 처리됩니다. async 함수는 항상 Promise를 반환하고, 호출자는 이를 .then()과 .catch()를 사용하여 처리할 수 있습니다.

비교적 간결하고 읽기 쉬운 구문으로 작성할 수 있는 async/await 문법은 비동기 코드를 작성하는 것을 더 쉽게 만들어줍니다. 또한 오류 처리를 위해 try/catch 문을 사용할 수 있어 예외 상황을 처리하기 용이합니다.


에러 처리 방법

비동기 작업에서 발생하는 에러를 처리하는 방법은 다양합니다. 여기에는 몇 가지 일반적인 에러 처리 방법이 포함되어 있습니다.

Promise에서의 에러 처리


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         // resolve(data);
         reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

fetchData()
   .then(function(data) {
      console.log("Data fetched successfully: " + data);
   })
   .catch(function(error) {
      console.log("Failed to fetch data: " + error.message);
   });
console.log("End");

async/await에서의 에러 처리


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         // resolve(data);
         reject(new Error("Failed to fetch data"));
      }, 2000);
   });
}

async function processAsync() {
   try {
      const data = await fetchData();
      console.log("Data fetched successfully: " + data);
   } catch (error) {
      console.log("Failed to fetch data: " + error.message);
   }
}

processAsync();
console.log("End");

위의 예시에서는 fetchData 함수가 Promise를 사용하여 비동기 작업을 수행하고 에러를 발생시킵니다. Promise에서의 에러 처리는 .catch() 메서드를 사용하여 처리됩니다. .catch() 블록 내에서 에러 메시지를 출력하거나 적절한 동작을 수행할 수 있습니다.

async/await에서의 에러 처리는 try/catch 문을 사용하여 처리됩니다. await 키워드를 사용하여 Promise의 완료를 기다릴 때, 에러가 발생하면 catch 블록으로 제어가 이동합니다. catch 블록 내에서 에러 메시지를 출력하거나 적절한 동작을 수행할 수 있습니다.

또한, Promise의 .finally() 메서드를 사용하여 작업의 완료를 알릴 수도 있습니다. .finally() 블록 내에서 clean-up 작업을 수행하거나 작업 완료와 관련된 로그를 출력할 수 있습니다.


Promise 체이닝 vs async/await

Promise 체이닝과 async/await은 모두 비동기 작업을 처리하는 방법입니다. 각각의 특징을 살펴보고 비교해보겠습니다.

Promise 체이닝


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
      }, 2000);
   });
}

fetchData()
   .then(function(data) {
      console.log("Data fetched successfully: " + data);
      return processData(data);
   })
   .then(function(processedData) {
      console.log("Data processed successfully: " + processedData);
   })
   .catch(function(error) {
      console.log("Failed to fetch or process data: " + error.message);
   });
console.log("End");

async/await


function fetchData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const data = "This is the fetched data";
         resolve(data);
      }, 2000);
   });
}

function processData(data) {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const processedData = data.toUpperCase();
         resolve(processedData);
      }, 2000);
   });
}

async function processAsync() {
   try {
      const data = await fetchData();
      console.log("Data fetched successfully: " + data);
      const processedData = await processData(data);
      console.log("Data processed successfully: " + processedData);
   } catch (error) {
      console.log("Failed to fetch or process data: " + error.message);
   }
}

processAsync();
console.log("End");

위의 예시에서는 fetchData 함수가 데이터를 가져오고, processData 함수가 데이터를 가공하는 비동기 작업을 수행합니다.

Promise 체이닝은 .then() 메서드를 사용하여 비동기 작업이 성공적으로 완료되었을 때 콜백 함수를 실행합니다. 첫 번째 .then()에서 데이터를 가져오고, 두 번째 .then()에서 데이터를 가공합니다. .catch()를 사용하여 작업이 실패하면 에러를 처리합니다. 이 방식은 작업이 성공적으로 완료될 때마다 다음 단계로 넘어갈 수 있는 유연성을 제공합니다.

async/await은 async 함수 내에서 await 키워드를 사용하여 비동기 작업의 완료를 기다립니다. 비동기 작업에 대해 보다 동기적인 코드 작성이 가능하며, 에러 처리는 try/catch 문을 사용하여 처리할 수 있습니다. 데이터를 가져온 후에 다음 작업을 처리하기 위해 await 키워드를 사용합니다. 이 방식은 비동기 코드를 직관적이고 읽기 쉬운 형태로 작성할 수 있으며, 예외 상황을 처리하기 용이합니다.

Promise 체이닝은 비동기 작업을 순차적으로 체인으로 연결하여 작업을 관리하는데 유용하며, async/await은 동기적인 코드 스타일로 작성할 수 있어 가독성이 높고 예외 처리가 용이합니다.


비동기 함수의 동시 실행

JavaScript에서 비동기 함수를 동시에 실행하려면 여러 가지 방법이 있습니다. 몇 가지 일반적인 방법을 살펴보고 비교해보겠습니다.

Promise.all()


function fetchUserData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const userData = "User data";
         resolve(userData);
      }, 2000);
   });
}

function fetchProductData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const productData = "Product data";
         resolve(productData);
      }, 2000);
   });
}

Promise.all([fetchUserData(), fetchProductData()])
   .then(function([userData, productData]) {
      console.log("User data: " + userData);
      console.log("Product data: " + productData);
   })
   .catch(function(error) {
      console.log("Failed to fetch data: " + error.message);
   });
console.log("End");

async/await와 Promise.all()


function fetchUserData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const userData = "User data";
         resolve(userData);
      }, 2000);
   });
}

function fetchProductData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const productData = "Product data";
         resolve(productData);
      }, 2000);
   });
}

async function fetchAndProcessData() {
   try {
      const [userData, productData] = await Promise.all([fetchUserData(), fetchProductData()]);
      console.log("User data: " + userData);
      console.log("Product data: " + productData);
   } catch (error) {
      console.log("Failed to fetch data: " + error.message);
   }
}

fetchAndProcessData();
console.log("End");

Promise.all() 메서드는 여러 개의 Promise 객체를 전달받아 모든 Promise의 완료를 기다린 후에 결과를 반환합니다. Promise.all()을 사용하여 여러 비동기 작업을 동시에 실행할 수 있습니다. 이는 Promise 객체의 배열을 전달하고, 결과는 .then() 블록에서 배열로 받아서 처리할 수 있습니다.

async/await와 Promise.all()을 함께 사용하면 각각의 비동기 함수를 동시에 실행하고, .then() 블록 대신에 await를 사용하여 결과를 받을 수 있습니다. await 키워드를 사용하여 Promise.all()의 결과를 기다리고, 비동기 함수 실행 결과의 배열을 변수에 할당할 수 있습니다.

두 예시에서 모든 비동기 작업이 완료된 후에 “User data”와 “Product data”를 출력합니다.


비동기 함수의 순차 실행

JavaScript에서 비동기 함수를 순차적으로 실행하려면 여러 가지 방법이 있습니다. 몇 가지 일반적인 방법을 살펴보고 비교해보겠습니다.

Promise 체이닝


function fetchUserData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const userData = "User data";
         resolve(userData);
      }, 2000);
   });
}

function processUserData(userData) {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const processedData = "Processed " + userData;
         resolve(processedData);
      }, 2000);
   });
}

fetchUserData()
   .then(function(userData) {
      console.log("User data fetched: " + userData);
      return processUserData(userData);
   })
   .then(function(processedData) {
      console.log("User data processed: " + processedData);
   })
   .catch(function(error) {
      console.log("Failed to fetch or process data: " + error.message);
   });
console.log("End");

async/await


function fetchUserData() {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const userData = "User data";
         resolve(userData);
      }, 2000);
   });
}

function processUserData(userData) {
   return new Promise(function(resolve, reject) {
      setTimeout(function() {
         const processedData = "Processed " + userData;
         resolve(processedData);
      }, 2000);
   });
}

async function sequentialExecution() {
   try {
      const userData = await fetchUserData();
      console.log("User data fetched: " + userData);
      const processedData = await processUserData(userData);
      console.log("User data processed: " + processedData);
   } catch (error) {
      console.log("Failed to fetch or process data: " + error.message);
   }
}

sequentialExecution();
console.log("End");

Promise 체이닝은 .then() 메서드를 사용하여 각 비동기 작업의 성공적인 완료를 처리합니다. 첫 번째 .then()에서는 데이터를 가져오고, 두 번째 .then()에서는 데이터를 처리합니다. .catch()를 사용하여 작업이 실패한 경우 에러를 처리합니다. 이 방식은 각 작업이 성공적으로 완료될 때마다 다음 작업으로 이동할 수 있도록 유연성을 제공합니다.

async/await은 비동기 함수 내에서 await 키워드를 사용하여 각 작업의 완료를 기다립니다. 비동기 함수 내에서 코드를 동기적이고 읽기 쉽도록 작성할 수 있으며, 에러 처리는 try/catch 문을 사용하여 처리할 수 있습니다. 데이터를 가져온 후에 다음 작업을 처리하기 위해 await 키워드를 사용합니다. 이 방식은 비동기 코드를 직관적이고 읽기 쉬운 형태로 작성할 수 있으며, 예외 상황을 처리하기 용이합니다.

두 예시에서는 fetchUserData() 함수가 데이터를 가져오고, processUserData() 함수가 데이터를 가공하는 비동기 작업을 수행합니다. 데이터를 가져온 후에 가공된 데이터를 출력합니다.


Leave a Comment