JavaScript

[JS중급] 비동기 실행과 Promise 객체

염두리안 2024. 11. 21. 17:40
728x90
반응형
  • fetch함수와 비동기 실행
    • 비동기 실행: 특정 작업을 시작(리퀘스트 보내기)하고, 완벽하게 다 처리(리스폰스를 받아서 처리)하기 전에, 실행 흐름이 바로 다음 코드로 넘어가고, 나주에 콜백이 실행되는 것 | 동기 실행에 비해 한 작업을 더 빠른 시간 내에 처리 가능
    • 동기 실행: 한번 시작한 작업은 다 처리하고 나서 다음 코드로 넘어가는 것
    • 비동기 실행 출력 순서: Start! - End - fetch함수
      • fetch함수의 then메서드는 콜백을 단지 등록만 함(바로 실행 X)
      • 그래서 End를 실행하고, 서버에 리스폰스가 도착하면 그 내용이 출력됨
    • 동기 실행 순서: Start! - fetch함수(리퀘스트보내기) - 리스폰스가 오기 전까지 코드 실행 일시 정지되고 오면 필요한 처리 수행 - End
console.log('Start!');

fetch('https://www.google.com')
    .then((response) => response.text())
    .then((result) => { console.log(result); });
    
console.log('End');
  • 비동기 실행 함수들
    • setTimeout() : 특정 함수의 실행을 원하는 시간만큼 뒤로 미루기 위해 사용
    • setInterval() : 특정 콜백을 일정한 시간 간격으로 실행하도록 등록하는 함수
    • addEventListener : DOM 객체의 메서드 | 만약 사용자가 웹에서 실행하고 싶은 함수가 있다면, ① 해당 DOM객체의 onclick속성에 그 함수를 설정하거나, ② 해당 DOM 객체의 addEventListener메서드의 파라미터로 전달하면 됨
// setTimeout(콜백, 시간) 
console.log('a');
// 2000ms 뒤로 미룸
setTimeout(() => { console.log('b'); }, 2000);
console.log('c');
>> a c undefined b

// setInterval(콜백, 시간)
console.log('a');
// b를 출력하는 콜백이 2초 간격으로 계속 실행
setInterval(() => { console.log('b'); }, 2000);
console.log('c');
>> a c undefined b ...
// onclick 속성
btn.onclick = function (e) { // 해당 이벤트 객체가 파라미터 e로 넘어옴
  console.log('Hello!');
};
// arrow function 형식
btn.onclick = (e) => {
  console.log('Hello!');
};

// addEventListener(이벤트 이름, 콜백)
btn.addEventListener('click', function (e) { // 해당 이벤트 객체가 파라미터 e로 넘어옴
  console.log('Hello Codeit!');
});
// 또는 arrow function 형식
btn.addEventListener('click', (e) => {
  console.log('Hello Codeit!');
});
  • fetch함수는 Promise객체를 리턴
    • Promise객체 : 어떤 작업에 관한 '상태 정보' | fetch함수가 성공할수도 실패할 수도 있는데, 이런 작업의 결과가 Promise 객체에 저장됨 => Promise를 보면 fetch 상태를 볼 수 있음
      • pending(진행중) 
      • fulfilled(성공) : 성공하게 되면 promise는 그 작업 성공 결과도 함께 갖게 됨
      • rejected(실패) : 실패하게 되면 promise는 작업 실패 정보를 가짐
    • fetch함수의 then메서드는 Promise객체의 메서드
  • Promise Chaining : Promise 객체에 then 메서드를 연속해서 붙이는 것
    • then 메서드는 각각의 새로운 promise 객체를 리턴
      • promise 객체를 리턴하는 경우 : 콜백에서 리턴한 promise 객체와 동일한 상태와 결과를 갖게 됨
      • promise 객체가 아닌 값을 리턴하는 경우 : then메서드가 리턴했던 promise 객체는 fulfilled 상태가 되고, 콜백의 리턴 값을 '작업성공결과'로 갖게 됨
    • text, json 메서드도 Promise 객체를 리턴함
      • text 메서드 : fetch함수로 리스폰스를 잘 받으면, response객체의 text메서드는 fulfilled 상태이면서 리스폰스의 바디에 있는 내용을 string 타입으로 반환한 값을 '작업성공결과'로 가진 Promise 객체를 리턴 | 이때 작업성공결과는 string 타입
      • json 메서드 : fetch함수로 리스폰스를 잘 받으면, response객체의 json메서드는 fulfilled 상태이면서, 리스폰스의 바디에 있는 json 데이터를 JS객체로 역직렬화해서 생겨난 객체를 '작업성공결과'로 가진 promise 객체를 리턴
    • 비동기 작업을 순차적으로 처리하기 위해서 promise chaining을 함
  • rejected 상태가 되면 실행할 콜백
    • 첫번째 콜백은 promise가 fulfilled 상태이기에 그 파라미터로 작업 성공 결과가 들어오지만, 두번째 콜백은 그 상태가 rejected 상태가 될 때 실행되기 때문에, 그 파라미터로 작업 실패 결과가 들어옴
      • 인터넷이 끊겨도 error 발생
fetch('https://google.com')
	// (response) => response.text() : promise가 fulfilled가 되면 실행
    // (error) => { console.log(error); : promise가 rejected가 되면 실행
	.then((response) => response.text(), (error) => { alert('Try again!'); })
    .then((result) => { console.log(result); });
  • then 메서드
    • 실행된 콜백이 어떤 값을 리턴하는 경우 : success든, error든 실행된 콜백에서 어떤 값을 리턴하는 경우 | 그 값의 종류에 따라 Promise 객체인 경우와 Promise 객체 이외의 경우로 나뉨
      • Promise를 리턴하는 경우 - 콜백에서 리턴하는 Promise객체를 then메서드가 그대로 리턴 → 콜백에서 리턴한 Promise 객체로부터 다시 Promise Chain이 쭉 이어져 감
      • Promise 객체 이외의 값을 리턴하는 경우 - 예를들어 fetch함수 작업이 실패해서 두번째 콜백인 error가 실행되는 경우, Try again!을 리턴하고, 해당 콜백을 등록한 then메서드가 리턴했던 Promise가 fulfilled 상태가 되고, 작업성공결과로 Try again 문자열을 갖게 됨
    • 실행된 콜백이 아무 값도 리턴하지 않는 경우 : alert 함수만 실행되고 끝나는 경우, 결과적으로 콜백은 아무런 값도 리턴하지 않은 것과 같음... JS에선 함수가 아무것도 리턴하지 않으면 undefined를 리턴한 것으로 간주됨
    • 실행된 콜백 내부에서 에러가 발생했을 때 : 정의되지 않은 함수를 콜백에서 사용하거나 throw문을 사용해 인위적으로 에러를 발생시키는 등 에러가 발생한다면, Promise 객체는 rejected 상태가 되고, 작업실패정보로 해당 에러 객체를 갖게 됨
    • 아무런 콜백도 실행되지 않을 때 : Promise-2의 경우, Promise-1객체처럼 rejected상태가 되고, 똑같은 작업 실패 정보를 갖게 됨 → rejected가 된 Promise-2의 then메서드는 이제 두번째 콜백이 존재하기 때문에 그 두번째 콜백이 실행됨 | 아무런 콜백도 실행되지 않는 경우엔 그 이전 Promise 객체의 상태, 결과가 그대로 이어짐!
fetch('https://www.google.com') // Promise-1
  .then((response) => response.text()) // Promise-2
  .then((result) => { console.log(result) }, (error) => { alert(error) });
  • catch 메서드 : then 메서드를 약간 변형시킨 것 | promise chain에서 어느 promise 상태가 rejected 되더라도 잘 대응하기 위해 보통 마지막에 사용
    • 중간에 에러가 발생해도 catch메서드가 그 대안을 뒤로 넘겨줄 수 있으면 catch메서드를 중간에 써도 됨
fetch('https://www.google.com')
  .then((response) => response.text())
  // promise객체가 rejected 상태가 되면 실행될 콜백을 등록하는 메서드 
  // .then(undefined, (error) => { console.log(error); }) 와 같은 의미
  .catch((error) => { console.log(error); })
  .then((result) => { console.log(result) }, (error) => { alert(error) });
  • finally 메서드 : 어떤 객체가 성공/실패 상관 없이 항상 실행시키고 싶은 콜백 | 작업실행상태 필요 X → 그래서 파라미터가 필요 없음 | catch메서드 뒤에 씀
fetch('https://www.google.com')
  .then((response) => response.text())
  .then((result) => { console.log(result); })
  .catch((error) => { console.log(error); })
  .finally(() => { console.log('exit'); });
  • Promise 객체 직접 생성하기 : 전통적인 형식의 비동기 실행 함수를 사용하는 코드를 Promise 기반의 코드로 변환하기 위해 사용하는 경우가 많음
    • Promisify를 하면 안되는 경우 : 콜백을 여러 번 실행하는 경우... Promise객체는 한번 pending 상태에서 fulfilled 또는 rejected 상태가 되고나면 그 뒤로는 그 상태와 결과가 바뀌지 않기 때문임
    • resolve : 생성된 Promise 객체를 fulfilled 상태로 만들 수 있는 함수가 연결됨
    • reject : 생성된 Promise 객체를 rejected 상태로 만들 수 있는 함수가 연결됨
// 생성 코드
// executor 함수 안에 resolve, reject가 들어가 있음
const p = new Promise((resolve, reject) => {

});

// 이미 상태가 결정된 Promise 객체 만들기
// fulfilled
const p = Promise.resolve('success');
// rejected
const p = Promise.reject(new Error('fail'));

// new 생성자&executor함수 사용하지 않고 resolve, reject 메서드를 사용하기
// resolve()
const p = Promise.resolve('success');
p.then((result) => { console.log(result); }, (error) => { console.log(error); });
// reject()
const p = Promise.reject(new Error('fail'));
p.then((result) => { console.log(result); }, (error) => { console.log(error); });
  • 여러 Promise객체를 다루기
    • all() : 여러 Promise 객체의 작업성공결과(fulfilled)를 기다렸다가 모두 한번에 취합하기 위해서 사용
    • race() : 아규먼트로 들어온 배열의 여러 Promise 객체들 중에서 가장 먼저 fulfilled/rejected 상태가 된 Promise객체와 동일한 상태와 결과를 갖게 됨
    • allSettled() : 배열 내 모든 Promise가 fulfilled/rejected 상태가 되기까지 기다리고, pending상태의 promise객체가 하나도 없게 되면, A의 상태값은 fulfilled가 되고 그 작업성공결과로 하나의 배열을 갖게 됨 | status프로퍼티(최종상태), value프로퍼티(작업성공결과), reason프로퍼티(작업실패정보)에 담은 객체들이 요소로 존재 | fulfilled + rejected = settled
    • any() : 여러 Promise객체들 중 가장 먼저 fulfilled 상태가 된 Promise객체의 상태와 결과가 A에도 똑같이 반영.. 만약 모든 Promise객체가 rejected 상태가 되면 AggregateError(작업실패정보)를 갖고 rejected 상태가 됨 | 배열 속 Promise객체 중 단 하나라도 fulfilled 상태가 되면 됨
// all
Promise
  .all([p1, p2, p3])
  .then((results) => {
    console.log(results); // Array : [1번 직원 정보, 2번 직원 정보, 3번 직원 정보]
  });

// race
Promise
  .race([p1, p2, p3])
  .then((result) => {
    console.log(result); // hello 출력
  })
  .catch((value) => {
    console.log(value);
  });
  • axios : fetch함수는 Ajax 통신을 하는 함수... => axios 외부 패키지 사용 | axios객체에서 리퀘스트를 보내는 많은 메서드들이 fetch함수처럼 Promise객체를 리턴
    • 장점) 모든 리퀘스트, 리스폰스에 대한 공통 설정 및 공통된 전처리 함수 삽입 가능 | serialization, deserialization을 자동으로 수행 | 특정 리퀘스트에 대해 얼마나 오랫동안 리스폰스가 오지 않으면 리퀘스트를 취소할지 설정 가능(request timeout) | 업로드 시 진행 상태 정보를 얻을 수 있음 | 리퀘스트 취소 기능 지원
    • 단점) 별도 다운로드가 필요한 패키지
// 예시
axios
  .get('https://google.com')
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });
728x90
반응형