[강의] async/await을 활용한 세련된 비동기 코드

김하은·2023년 11월 15일
0

코드잇 강의 정리

목록 보기
44/60

async/await이란?

  • 프로미스 객체를 조금 더 간단하게 다룰 수 있는 문법
fetch('https://jsonplaceholder.typicode.com/users')
  .then((response) => response.text())
  .then((result) => { console.log(result); });

// 위 코드와 동일하게 동작함
async function fetchAndPrint() {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const result = await response.text();
    console.log(result);
}

fetchAndPrint();
  • async: asynchronous의 줄임말로 비동기를 의미함
    • 함수 안에 비동기적으로 실행될 부분이 있다는 것을 의미함
    • 함수의 코드 중에서 프로미스 객체를 리턴하는 코드가 있다는 뜻
  • await: 그 뒤에 코드를 실행하고 그 코드가 리턴하는 프로미스 객체가 fulfilled 상태, rejected 상태가 될 때까지 기다려 줌
    • fulfilled 상태가 되고 그 작업 성공 결과를 리턴한 후에 그 다음 줄을 실행함
    • await 키워드는 async 함수 안에서만 사용할 수 있음

async/await 구문의 실행 원리

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

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);

동기 실행 코드처럼 생긴 비동기 실행 코드

  • 위 코드의 원래 모습
/* fetch('https://www.google.com')
    .then((response) => response.text())
    .then((result) => { console.log(result); }); */

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

console.log(1);
fetchAndPrint();
console.log(3);
console.log(4);
console.log(5);
console.log(6);
  • async/await 구문을 사용하면,
    1. Promise 객체를 사용할 때, then 메소드 등을 사용하지 않고도
    2. 마치 동기 실행 코드처럼 코드를 훨씬 더 간단하고 편하게 작성할 수 있으며 코드를 읽기에도 훨씬 편함
  • async 함수 안의 코드가 실행되다가 await을 만나면, 일단 await 뒤의 코드가 실행되고, 코드의 실행 흐름이 async 함수 바깥으로 나가서 나머지 코드를 다 실행함
  • 그 이후, await 뒤에 있던 Promise 객체가 fulfilled 상태가 되기를 기다립니다. 그리고 기다리던 Promise 객체가 fulfilled 상태가 되면 await이 Promise 객체의 작업 성공 결과를 리턴함

catch문과 finally문

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 함수는 Promise 객체를 리턴합니다

  • async가 붙은 함수는 항상 프로미스 개체를 리턴함
  • 함수 안에서 무슨 값을 리턴한지에 따라서 결국 어떤 프로미스 객체가 리턴되는지가 달라짐

async 함수가 리턴하는 Promise 객체

  • async 함수가 결국 Promise 객체를 리턴한다는 사실은 아주 중요함
  • 왜냐하면 이 말은 곧 async 함수 안에서 다른 async 함수를 가져다가 쓸 수 있다는 뜻이기 때문임

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

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

  • 해당 Promise 객체와 동일한 상태와 작업 성공 결과(또는 작업 실패 정보)를 가진 Promise 객체를 리턴함
  • pending 상태의 Promise 객체, 이미 fulfilled 상태인 Promise 객체, 이미 rejected 상태인 Promise 객체를 리턴하는 경우 전부 다 해당됨
// pending 상태
async function fetchAndPrint() {
  return new Promise((resolve, reject)=> {
    setTimeout(() => { resolve('abc'); }, 4000);
  });
}

fetchAndPrint();

// 이미 fulfilled 상태
async function fetchAndPrint() {
  return Promise.resolve('Success');
}

fetchAndPrint();

// 이미 rejected 상태
async function fetchAndPrint() {
  return Promise.reject(new Error('Fail'));
}

fetchAndPrint();

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

  • Promise 객체 이외에 숫자나 문자열, 일반 객체 등을 리턴하는 경우에는, fulfilled 상태이면서, 리턴된 값을 작업 성공 결과로 가진 Promise 객체를 리턴함
async function fetchAndPrint() {
  const member = {
    name: 'Jerry',
    email: 'jerry@codeitmall.kr',
    department: 'sales',
  };

  return member;
}

fetchAndPrint();

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

  • fulfilled 상태이면서, undefined를 작업 성공 결과로 가진 Promise 객체가 리턴됨
async function fetchAndPrint() {
  console.log('Hello Programming!');
}

fetchAndPrint();

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

  • rejected 상태이면서, 해당 에러 객체를 작업 실패 정보로 가진 Promise 객체가 리턴됨
async function fetchAndPrint() {
  throw new Error('Fail');
}

fetchAndPrint();


async 함수 안의 async 함수

  • async 함수 안에서는 또 다른 async 함수를 자유롭게 사용할 수 있음
  • async 함수는 결국 프로미스 객체를 리턴하니까 그 앞에 await을 붙여서 쓰면 됨

async를 붙이는 위치

// 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 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에 리퀘스트를 보내고 리스폰스를 받아서 출력하고 나서야, 다음 URL에 대한 리퀘스트를 보낼 수 있다는 즉, 순차적인 작업 처리를 한다는 문제점이 있음
  • 이전 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 함수로 감싸줬음
  • 각 URL에 대해서 fetch 함수를 실행해서 리퀘스트를 보내는 것을 순서대로 바로 실행해버림
  • 모든 URL에 대한 리퀘스트를 쭉 보내버리고, 먼저 리스폰스가 오는 순서대로 그 내용이 출력됨
  • 의도치 않게 순차 처리를 수행하는 비효율적인 코드를 짜지 않으려면 async 함수 안에서 무언가를 반복 처리하는 코드를 쓸 때 유의해야 함
  • 순차적인 처리가 필요한 경우가 아니라면 방금 본 것처럼 각 작업을 다시 async 함수로 묶어주면 됨

두 가지 종류의 콜백(심화)

  • 콜백: 함수의 파라미터로 전달되는 함수를 의미하는 넓은 의미의 개념
  • 두 종류로 나뉨
    1. 동기 실행되는 콜백과
    2. 비동기 실행되는 콜백
  • filter 메소드는 동기 실행되는 콜백임
    • isOdd 콜백이 비동기 실행되는 콜백이었다면 그 뒤의 console.log(newArr);가 먼저 실행되어야 함
const arr = [1, 2, 3, 4, 5, 6];

const newArr = arr.filter(function isOdd(num) {
  return (num % 2 === 1); 
});

console.log(newArr); // [1, 3, 5]
profile
아이디어와 구현을 좋아합니다!

1개의 댓글

comment-user-thumbnail
2023년 11월 15일

글 재미있게 봤습니다.

답글 달기