FE

[스터디] 9주차 자바스크립 (모던 자바스크립트 deep dive)

올바른생활부터 2025. 1. 28. 23:40
728x90
반응형
SMALL

Promise, async/await

질문 1. 동기와 비동기는 무엇인가요?

  • 동기 : 현재 실행 중인 코드가 완료된 후에 다음 코드를 실행하는 방식 입니다.
    • CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드
  • 비동기: 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어갑니다.
    • setTimeout, addEventListener, XMLHttpRequest 등

질문 2. Promise은 무엇이고, Promise의 상태에 대해서 설명해주세요

  • ES6에서 비동기 처리를 위한 패턴으로 프로미스를 도입했고, Promise 객체는 비동기 작업을 완료(fulfilled) 또는 실패(rejected)와 결과 값을 나타내는 객체 입니다.

Promise는 세 가지 상태를 가질 수 있습니다.

  1. Pending (대기): 초기 상태, 비동기 작업이 아직 완료되지 않은 상태.
  2. Fulfilled (이행): 비동기 작업이 성공적으로 완료된 상태. resolve 함수를 호출하여 이 상태로 변경됩니다.
  3. Rejected (실패): 비동기 작업이 실패한 상태. reject 함수를 호출하여 이 상태로 변경됩니다.

Promise 상태 변경

  1. Pending → Fulfilled: 비동기 작업이 성공하면 resolve(value)가 호출되어 Promise는 fulfilled 상태가 됩니다. value는 성공 결과입니다.
  2. Pending → Rejected: 비동기 작업이 실패하면 reject(reason)가 호출되어 Promise는 rejected 상태가 됩니다. reason은 실패 이유입니다.
// Promise 생성
let promise = new Promise((resolve, reject) => {
    let success = true; // 비동기 작업의 성공 여부를 나타내는 변수 (예시)

    // 비동기 작업 (예: 타임아웃)
    setTimeout(() => {
        if (success) {
            resolve("작업이 성공했습니다!"); // 작업 성공 시 resolve 호출
        } else {
            reject("작업이 실패했습니다."); // 작업 실패 시 reject 호출
        }
    }, 1000);
});

// Promise 사용
promise.then((result) => {
    console.log(result); // "작업이 성공했습니다!" 출력
}).catch((error) => {
    console.error(error); // "작업이 실패했습니다." 출력
});

// 설명: promise가 생성된 후 1초 뒤에 resolve 또는 reject를 호출하여 상태를 변경한다.
//  then 메서드는 Promise가 fulfilled 상태일 때 실행되고, catch 메서드는 Promise가 rejected 상태일 때 실행

질문 3. Promise.all과 Promise.allSettled, Promise.race에 대해 설명해주세요

Promise.all:

  • Promise.all 메서드는 여러 개의 비동기 처리를 한꺼번에 병렬 처리할 때 사용한다.
  • 모든 프로미스가 성공(fulfilled)하면 그 결과를 배열로 반환한다. 하지만, 하나의 프로미스라도 실패(rejected)하면 즉시 실패하고, 나머지 프로미스의 결과는 무시된다.
# 예시 1
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // 예상 출력: [3, 42, "foo"]
  })
  .catch((error) => {
    console.error(error);
  });

// 위 예시에서 promise1, promise2, promise3 모두 성공(fulfilled)하면 Promise.all은 [3, 42, "foo"]를 출력합니다.
// 만약 하나라도 실패(rejected)하면 catch 블록이 실행됩니다.

# 예시 2.
const promise1 = Promise.resolve('Success 1');
const promise2 = Promise.reject('Failure');
const promise3 = Promise.resolve('Success 3');

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values); // 이 블록은 실행되지 않습니다.
  })
  .catch((error) => {
    console.error(error); // 출력: Failure
  });

Promise.allSettled:

  • Promise.allSettled 메서드는 프로미스를 요소로 갖는 배열을 인수로 받아, 모든 프로미스가 완료(settled)될 때까지 기다린다. 프로미스가 성공(fulfilled)했는지 실패(rejected)했는지와 상관없이 결과를 배열로 반환한다.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, 'error');
});
const promise3 = Promise.resolve('foo');

Promise.allSettled([promise1, promise2, promise3])
  .then((results) => {
    results.forEach((result) => console.log(result));
  });
  
// promise1이 성공(fulfilled), promise2가 실패(rejected), promise3이 성공(fulfilled)하는데, 
// Promise.allSettled는 각 프로미스의 결과를 배열로 반환합니다. 출력은 다음과 같습니다:  
  
// 결괏값
// { status: 'fulfilled', value: 3 }
// { status: 'rejected', reason: 'error' }
// { status: 'fulfilled', value: 'foo' }

Promise.race:

  • Promise.race 메서드는 프로미스를 요소로 갖는 배열의 이터러블을 인수로 전달받는다. 전달된 프로미스들 중 가장 먼저 완료된(fulfilled 또는 rejected) 프로미스의 결과만을 반환한다.
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'First');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'Second');
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(reject, 200, 'Third');
});

Promise.race([promise1, promise2, promise3])
  .then((value) => {
    console.log(value); // 예상 출력: 'Second'
  })
  .catch((error) => {
    console.error(error);
  });
  
// promise1은 500ms 후에 성공(fulfilled)하며, 값으로 'First'를 반환합니다.
// promise2는 100ms 후에 성공(fulfilled)하며, 값으로 'Second'를 반환합니다.
// promise3는 200ms 후에 실패(rejected)하며, 이유로 'Third'를 반환합니다.

// 만약 promise2가 없고 promise3가 가장 빨리 완료되었다면, 
// Promise.race는 promise3의 실패 이유인 'Third'를 반환하게 됩니다. 

질문 5. 자바스크립트에서 Callback을 사용하는 상황 세 가지를 설명해주세요.

# 1. 이벤트 리스너로 사용
## addEventListener는 특정 이벤트가 발생했을 때 콜백함수를 실행하는 메서드이다.

let button = document.getElementById("button"); // 버튼 요소를 선택

// 버튼에 클릭 이벤트 리스너를 추가
button.addEventListener("click", function () { // 콜백 함수
  console.log("Button clicked!"); 
});

# 2. 고차함수에 사용
## 자바스크립트에서 for문 보다 더 자주 사용되는 반복문이 forEach 메서드일 것이다. 
forEach 메서드의 입력값으로 콜백 함수를 전달하는 형태임을 볼 수 있다.

// 예시 : 배열의 각 요소를 두 배로 곱해서 새로운 배열을 생성하는 콜백 함수 
let numbers = [1, 2, 3, 4, 5]; // 배열 선언 
let doubled = []; // 빈 배열 선언 

// numbers 배열의 각 요소에 대해 콜백 함수 실행 
numbers.forEach(function (num) { 
    doubled.push(num * 2); // 콜백 함수로 각 요소를 두 배로 곱해서 doubled 배열에 추가 
}); 

console.log(doubled); // [2, 4, 6, 8, 10]

# 3. 타이머 실행 함수로 사용 
setTimeout이나 setInterval과 같은 타이머 함수에서 
일정 시간마다 스크립트를 실행하는 용도로서 콜백 함수를 이용한다.

// 3000 밀리초(3초) 후에 콜백 함수 실행
setTimeout(function () {
  console.log("Time is up!"); // 콜백 함수로 콘솔에 메시지 출력
}, 3000);

# 4. 애니메이션 완료
## jQuery에서 제공하는 애니메이션 메서드들은 애니메이션이 끝난 후에 실행할 콜백 함수를 인자로 받는다.

// jQuery의 slideUp 메서드를 사용하여 #box 요소를 숨기고 콜백 함수로 콘솔에 메시지 출력
$("#box").slideUp(1000, function () {
  console.log("Animation completed!"); // 콜백 함수로 콘솔에 메시지 출력
});

# 5. Ajax 결과값을 받을 때 사용
## 서버와 데이터를 주고받을 때 사용하는 fetch 메서드의 서버 요청의 결과값을 처리하기 위해 콜백 함수가 사용된다.

// fetch 메서드를 사용하여 서버로부터 JSON 데이터를 받아오고 콜백 함수로 화면에 출력
fetch("<https://jsonplaceholder.typicode.com/users>")
  .then(function (response) {
    // fetch 메서드가 성공하면 콜백 함수로 response 인자를 받음
    return response.json(); // response 객체의 json 메서드를 호출하여 JSON 데이터를 반환
  })
  .then(function (data) {
    // json 메서드가 성공하면 콜백 함수로 data 인자를 받음
	console.log(data);
  })

질문 6. callback Hell과 Promise와 차이점에 설명해주세요.

  • 콜백 헬은 코드의 가독성이 떨어지는 반면에, 프로미스는 체이닝을 활용하여 코드의 가독성이 콜백함수에 비해 상대적으로 쉽다.
# callback hell
function firstFunction(callback) {
  setTimeout(() => {
    console.log('First function');
    callback();
  }, 1000);
}

function secondFunction(callback) {
  setTimeout(() => {
    console.log('Second function');
    callback();
  }, 1000);
}

function thirdFunction(callback) {
  setTimeout(() => {
    console.log('Third function');
    callback();
  }, 1000);
}

firstFunction(() => {
  secondFunction(() => {
    thirdFunction(() => {
      console.log('All functions completed');
    });
  });
});
# promise
function firstFunction() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('First function');
      resolve();
    }, 1000);
  });
}

function secondFunction() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Second function');
      resolve();
    }, 1000);
  });
}

function thirdFunction() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Third function');
      resolve();
    }, 1000);
  });
}

firstFunction()
  .then(() => secondFunction())
  .then(() => thirdFunction())
  .then(() => {
    console.log('All functions completed');
  })
  .catch((error) => {
    console.error(error);
  });
  • 이와 같은 코드에서 각 비동기 함수는 프로미스를 반환하며, then 메서드를 통해 순차적으로 실행된다. 이렇게 체이닝을 통해 코드의 가독성이 높아지고, 비동기 작업의 흐름을 쉽게 파악할 수 있다.

질문 7. async/await에 개념과 사용법에 대해 설명해주세요.

개념:

  • async/await은 ES8에서 제너레이터보다 간단하고 가독성 좋게 비동기 처리를 동기 처리처럼 동작하도록 구현할 수 있게 도입되었다. async/await을 사용하면 프로미스의 then/catch/finally 후속 처리 메서드 없이 마치 동기 처리처럼 프로미스가 처리 결과를 반환하도록 구현할 수 있다.
  const handleDelete = async (id) => {
    await deleteDoc(doc(db, "todos", id));
  };
  • handleDelete 함수에서는 Firestore 데이터베이스에 대한 비동기 작업을 수행하고 있습니다. 이 함수는 async 키워드로 선언되었으며, await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다립니다.
  • handleDelete함수에 async await 뺸다면 async/await를 사용하지 않는 handleDelete 함수가 Firestore 데이터베이스에서 문서를 삭제하는 비동기 작업을 시작하지만, 그 작업이 완료되기를 기다리지 않고 바로 함수(다음 코드를 실행)를 종료합니다. 이후에 비동기 작업이 완료되면 그 결과는 처리되지 않고 무시됩니다.

사용법:

  • async 와 await 는 function 키워드 앞에 async 만 붙여주면 되고, 비동기로 처리되는 부분 앞에 await 만 붙여주면 된다.
#  콜백 함수에서 프로미스를 then으로 처리하는 방법
function delay(ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(`Waited for ${ms} milliseconds`);
    }, ms);
  });
}

delay(2000).then(message => {
  console.log(message);  // 2초 후에 'Waited for 2000 milliseconds' 출력
}).catch(error => {
  console.error(error);
});

// delay 함수는 ms 밀리초 후에 resolve를 호출하는 프로미스를 반환합니다.
// setTimeout을 이용해 ms 밀리초 후에 resolve 함수를 호출하여 프로미스가 완료되도록 합니다.
//  delay(2000)을 호출하여 2초 후에 프로미스가 해결되도록 하고, then 메서드를 사용하여 해결된 후의 작업을 처리합니다.

# async와 await을 이용하는 방법
function delay(ms) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(`Waited for ${ms} milliseconds`);
    }, ms);
  });
}

async function asyncDelayExample() {
  try {
    const message = await delay(2000);
    console.log(message);  // 2초 후에 'Waited for 2000 milliseconds' 출력
  } catch (error) {
    console.error(error);
  }
}

asyncDelayExample();

 

결론적으로 1)콜백 헬의 단점으로 → 2) Promise는 콜백 헬을 해결하고자 하였고, then() 메서드 체이닝을 통해 비동기 코드를 좀 더 명확하게 작성할 수 있게 되었다. 하지만 then() 메서드 체이닝이 복잡성을 줄이긴 했지만, 여전히 비동기 코드를 작성하는 것에 있어서 가독성과 이해하기 쉬운 코드를 만드는 데 어려움이 있었다. → 3)그래서 나온 것이 async/await다.
728x90
반응형
LIST