자바스크립트 웹 개발 기본기 (3) (async/await)

LeeKyungwon·2024년 4월 1일
0

프론트엔드 

목록 보기
17/56
post-custom-banner

async/await 란?

async function fetchAndPrint(){
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const result = await response.text();
  console.log(result);
}

fetchAndPrint();

await -> 어떤 프로미스 객체를 리턴하는 코드 앞에 붙여져 있음

async : 비동기, 함수 안에 비동기적으로 실행될 부분이 있다는 것을 의미

await : 그 뒤의 코드를 실행하고 리턴하는 프로미스 객체를 기다려줌 (프로미스 객체가 fulfilled 상태 또는 rejected 상태가 될 때까지)
fulfilled 상태가 되면 작업 성공 결과를 리턴해서 돌려줌

async/await 실행 원리

await은 async가 붙은 함수 안에서만 쓸 수 있다.

async function fetchAndPrint(){
  console.log(2);
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  console.log(7);
  const result = await response.text();
  console.log(result);
}

console.log(1);
fetchAndPrint();
console.log(3);
console.log(4);
console.log(5);
console.log(6);

fetchAndPrint 함수는 동기 실행처럼 생겼지만 비동기 실행 함수이다.
이렇게 생긴 이유는 async/await 구문 자체가 기존의 Promise 객체를 사용하는 코드(Promise Chaining)를

(1) 개발자가 더 편하게 작성할 수 있도록 하고
(2) 코드의 가독성을 높이기 위해서
도입된 Syntactic sugar(기존 문법을 더 편하게 사용할 수 있도록 하는 문법적 장치)에 해당하기 때문이다.

catch, finally 문

async function fetchAndPrint(){
  try{
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const result = await response.text();
    console.log(result);
  } catch (error) {
    console.log(error);
  } finally {
    console.log('exit');
  }
}
fetchAndPrint();

async 함수는 Promise 객체를 리턴한다.

async 함수 안에서 리턴하는 값의 종류에 따라 결국 어떤 Promise 객체를 리턴하게 되는지 살펴보겠다.

1. 어떤 값을 리턴하는 경우

(1) Promise 객체를 리턴하는 경우

async 함수 안에서 Promise 객체를 리턴하는 경우에는 해당 Promise 객체와 동일한 상태와 작업 성공 결과(또는 작업 실패 정보)를 가진 Promise 객체를 리턴한다.(그냥 해당 Promise 객체를 리턴한다고 봐도 괜찮음)

async function fetchAndPrint() {
  return new Promise((resolve, reject)=> {
    setTimeout(() => { resolve('abc'); }, 4000);
  });
}

fetchAndPrint();

(2) Promise 객체 이외의 값을 리턴하는 경우

async 함수 내부에서 Promise 객체 이외에 숫자나 문자열, 일반 객체 등을 리턴하는 경우에는, fulfilled 상태이면서, 리턴된 값을 작업 성공 결과로 가진 Promise 객체를 리턴한다.

2. 아무 값도 리턴하지 않는 경우

async function fetchAndPrint() {
  console.log('Hello Programming!');
}

fetchAndPrint();

fulfilled 상태이면서, undefined를 작업 성공 결과로 가진 Promise 객체가 리턴된다.

3. async 함수 내부에서 에러가 발생했을 때

async function fetchAndPrint() {
  throw new Error('Fail');
}

fetchAndPrint();

async 함수 안에서 에러가 발생하면, rejected 상태이면서, 해당 에러 객체를 작업 실패 정보로 가진 Promise 객체가 리턴된다.

async 함수 안의 async 함수

async 함수는 항상 Promise 객체를 리턴
async 함수 안에 async 함수를 넣을 수 있다.
다른 함수 안에 넣을 때, 함수 앞에 await을 붙이면 됨

async를 붙이는 위치

자바스크립트에서 함수를 표현하는 방법

  1. Function Declaration(함수 선언식)
  2. Function Expression(함수 표현식)
    2-1. 함수에 이름이 붙어있는 Named Function Expression
    2-2. 함수에 이름이 없는 Anonymous Function Expression
  3. Arrow Function(화살표 함수)
// 1) Function Declaration
async function example1(a, b) {
  return a + b;
}

// 2-1) Function Expression(Named)
const example2_1= async function add(a, b) {
  return a + b;
};

// 2-2) Function Expression(Anonymous)
const example2_2 = async function(a, b) {
  return a + b;
};

// 3-1) Arrow Function
const example3_1 = async (a, b) => {
  return a + b;
};

// 3-2) Arrow Function(shortened)
const example3_2 = async (a, b) => a + b;

즉시 실행 함수의 경우에는 다음과 같이 async를 붙일 수 있다.

(async function print(sentence) {
  console.log(sentence);
  return sentence;
}('I love JavaScript!'));

(async function (a, b) {
  return a + b;
}(1, 2));

(async (a, b) => {
  return a + b; 
})(1, 2);

(async (a, b) => a + b)(1, 2);

async 함수의 성능 문제

async function getResponses(urls) {
  for(const url of urls){
    const response = await fetch(url);
    console.log(await response.text());
  }
}

순차적인 작업 처리를 하는 코드이다. (이전 URL에 대해서 await 문이 붙은 Promise 객체가 fulfilled 상태가 될 때까지는 그 다음 URL에 대한 작업들이 시작될 수 없기 때문에)
하지만 출력 순서는 상관없이 모든 리스폰스의 내용이 출력되기만 하면 된다면, 이 코드는 성능 관점에서 아쉬운 코드이다.

async function fetchUrls(urls){
  for(const url of urls){
    (async () => { // 추가된 부분!
      const response = await fetch(url);
      console.log(await response.text());
    })(); // 추가된 부분!
  }
}

이렇게 코드를 고치면 모든 URL에 대한 리퀘스트를 쭉 보내버리고, 먼저 리스폰스가 오는 순서대로 그 내용이 출력된다.

순차적인 처리가 필요한 경우가 아니라면 각 작업을 다시 async 함수로 묶어주면 된다.

비동기 실행 문법 총정리

비동기 실행이란

  • 특정 작업이 시작되고, 그 작업이 모두 완료되기 전에 바로 다음 코드가 실행되는 방식의 실행, 나머지 작업은 나중에 콜백을 통해 수행되는 방식의 실행
  • 특정 처리를 나중으로 미루는 방식의 실행
  • 콜백을 등록해두고, 추후에 특정 조건이 만족되면 그 콜백으로 나머지 작업을 수행하는 방식의 실행

1. 파라미터로 바로 콜백을 전달하는 형태의 전통적인 비동기 실행 함수

setTimeout, setInterval 함수, DOM 객체의 addEventListener 메소드 등

setTimeout(() => {
  console.log('asynchronously executed');
}, 2000);

button.addEventListener('click', (event) => { console.log('You Clicked'); });

함수의 파라미터로 콜백을 바로 전달하는 방식은 많은 경우에 쓰이고 있지만,
여러 비동기 작업의 순차적인(sequential) 처리가 필요한 경우에는 콜백 헬이라는 문제가 발생할 수도 있다.

fs.readFile('file1.txt', 'utf8', (error1, data1) => {
  if (error1) {
    console.log(error1);
  } else {
    console.log(data1);
    fs.readFile('file2.txt', 'utf8', (error2, data2) => {
      if (error2) {
        console.log(error2);
      } else {
        console.log(data2);
        fs.readFile('file3.txt', 'utf8', (error3, data3) => {
          if (error3) {
            console.log(error3);
          } else {
            console.log(data3);
          }
        });
      }
    });
  }
});

2. Promise

fetch('https://www.google.com')
  .then((response) => response.text())
  .then((result) => { console.log(result); })
  .catch((error) => { console.log(error); })
  .finally(() => { console.log('exit'); });

Promise 객체를 사용하면 콜백 헬 문제를 방지하면서, 여러 비동기 작업을 순차적으로 처리할 수 있다.

function readFile_promisified(filename) {
  const p = new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (error, data) => {
      if (error) {
        reject(error); // 에러 발생 시 -> rejected
      } else {
        resolve(data); // 파일 내용 읽기 완료 -> fulfilled
      }
    });
  });
  return p;
}

기존의 1.과 같은 전통적인 비동기 실행 함수들 중에서도 그 콜백이 단 한 번만 실행되는 함수들은 이런 식으로 Promisify해서 콜백 헬의 가능성을 없애고, Promise Chain 안에서 그 콜백의 리턴값을 사용할 수 있다.

3. async / await 구문

async function fetchAndPrint() {
  try {
    const response = await fetch('https://www.google.www');
    const result = await response.text();
    console.log(result);
  } catch(error) {
    console.log(error);
  } finally {
    console.log('exit');
  }
}

fetchAndPrint();

async/await 구문은 Promise 객체를 다루는 코드(Promise Chaining 코드 등)를 사람들이 좀더 익숙하게 느끼는 동기 실행 스타일의 코드로 작성할 수 있게 해주는 Syntactic sugar이다.

async 함수 안의 내용이 순차적으로 실행되다가도, await 문을 만나면 await 바로 뒤에 붙은 코드를 실행해두고, 일단은 함수 바깥으로 코드 흐름이 바뀐다.

두 가지 종류의 콜백

자바스크립트에서 콜백은 어떤 함수의 파라미터로 전달되는 모든 함수를 의미하는 개념이기 때문에, 어떤 함수의 파라미터로 전달되기만 한다면 해당 함수는 그 함수의 콜백이 된다.

  1. 동기 실행되는 콜백
    filter 메소드 : 배열의 여러 요소들 중에서 특정 조건을 만족하는 요소들만을 추려서 그 요소들로만 이루어진 새로운 배열을 리턴하는 메소드
  2. 비동기 실행되는 콜백
    지금까지 본 콜백들
post-custom-banner

0개의 댓글