본문 바로가기

Language/JavaScript

<자바스크립트> 프라미스 체이닝과 에러

 

1. 프라미스 체이닝

* 체이닝

 - 콜백에서 언급했듯이 비동기 작업을 순차적으로 처리해야할 상황이 있다. 이 때 프라미스 체이닝을 사용하며 효율적으로 이를 다룰 수 있다.

 

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) {

  console.log(result); // 1
  return result * 2;

}).then(function(result) {

  console.log(result); // 2
  return result * 2;

}).then(function(result) {

  console.log(result); // 4
  return result * 2;

});

 

 - setTimeout으로 1초후에 프라미스가 이행되어 result로 1이 전달된다. 그리고 then이 순차적으로 호출되며 반환 값을 result로 다음으로 넘긴다.

 

 - 위와 같은 체이닝이 가능한 이유는 promise.then을 호출하면 프라미스가 반환되기 때문이다. 프라미스가 반환되므로 result가 전달되고 .then을 또 호출할 수 있는 것이다.

 

* 프라미스 반환

 - then(handler)에서 사용된 핸들러가 프라미스를 생성하거나 반환할 수도 있다. 이 때에는 이어지는 핸들러가 프라미스가 처리될 때까지 기다리고, 처리되면 그 결과를 받는다.

 

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
}).then(function(result) {
  console.log(result); // 1
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
}).then(function(result) {
  console.log(result); // 2
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
}).then(function(result) {
  console.log(result); // 4
});

 

 - 위 코드는 1초 간격으로 1, 2, 4가 출력된다.

 

 - 예시로 순서대로 스크립트를 불러와보자. 스크립트를 불러오는 함수인 loadScript는 콜백 장을 참고하자.

 

loadScript("./harry/one.js")
  .then(function(script) {
    return loadScript("./harry/two.js");
  })
  .then(function(script) {
    return loadScript("./harry/three.js");
  })
  .then(function(script) {
    one();
    two();
    three();
  });

 

 - 화살표함수로 줄이면 다음과 같다.

 

loadScript("./harry/one.js")
  .then(script => loadScript("./harry/two.js"))
  .then(script => loadScript("./harry/three.js"))
  .then(script => {
    one();
    two();
    three();
  });

 

 - loadScript를 호출할 때마다 프라미스가 반환되고 다음 .then은 그 후에 실행된다.

 

 - 위와 같은 방식으로 콜백에서 만났던 문제를 해결할 수 있다.

 

 

2. fetch와 체이닝

* fetch와 체이닝

 - 프론트 단에서 네트워크 요청을 하는 경우를 생각해보자. 서버에 요청을 할때에 비동기작업을 처리할 일이 많다.

 

 - 사용자의 프로필 이미지를 요청하고 출력하는 예시를 보자.

 

fetch('https://api.harry.com/users/harry')
  .then(response => response.json())
  .then(user => {
    let img = document.createElement('img');
    img.src = user.profile_img;
    img.className = "profile-image";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000);
  });

 

 - fetch(url)을 실행하면 url에 네트워크 요청을 보내고 프라미스를 반환한다. 응답을 받으면 프라미스는 response 객체와 함께 이행된다.

 

 - response.text() 또는 response.json()은 서버에서 받은 데이터를 텍스트나 JSON으로 result 값으로 이행된 프라미스를 반환한다.

 

 - 위는 서버에 harry라는 유저의 정보를 요청하고 response.json()으로 JSON으로 파싱한다.

 

 - 그리고 이미지를 문서에 추가하고 3초뒤에 지우도록 하였다.

 

 - 하지만 위 코드에는 한 가지 문제가 있다. 만약 이미지가 사라진 이후에 다른 작업을 하고싶다면 할 수 있는 방법이 없다.

 

 - 체인을 확장하려면, 이미지가 사라질 때 이행 프라미스를 반환해야한다.

 

fetch('https://api.harry.com/users/harry')
  .then(response => response.json())
  .then(user => new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = user.profile_img;
    img.className = "profile-image";
    document.body.append(img);

    setTimeout(() => {
      img.remove()
      resolve(user);
    }, 3000);
  }))
  .then(user => console.log('Finish!'))

 

 - 위와 같이 setTimeout안에 resolve를 넣어서 처리가 되면 new Promise가 반환되도록 하였다. 비동기 동작은 항상 프라미스를 반환하도록 하는 것이 좋다.

 

 - 위를 함수로 정리하여 빼면 다음과 같다.

 

function showProfile(user) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = user.profile_img;
    img.className = "profile-image";
    document.body.append(img);

    setTimeout(() => {
      img.remove()
      resolve(user);
    }, 3000);
  })
}

fetch('https://api.harry.com/users/harry')
  .then(response => response.json())
  .then(showProfile)
  .then(user => console.log('Finish!'))

 

3. 에러 핸들링

* 숨겨진 try catch

 - 프라미스 체인은 프라미스가 거부되면 제일 가까운 rejection핸들러로 넘어간다.

 

 - 위에서 사용한 예시에서 에러가 발생한다고 가정하자.

 

fetch('https://api.harry.com/users/harry')
  .then(response => response.json())
  .then(showProfile)
  .then(user => console.log('Finish!'))
  .catch(error => console.log(error));

 

 - 체인 끝에 catch만 붙이더라도 프라미스 중 하나라도 거부되면 곧바로 catch가 에러를 잡는다.

 

 - 프라미스 생성자와 핸들러 주위에는 숨겨진 try catch가 있다. throw를 사용해서 에러를 던지지 않고 아래처럼 reject로도 에러를 만들 수 있다.

 

new Promise((resolve, reject) => {
  reject(new Error("에러 발생!"));
}).catch(alert); // Error: 에러 발생!

 

 - throw나 reject가 아닌 모든 종류의 에러가 암시적 try catch에서 처리된다.

 

* 다시 던지기

 - 일반적인 try catch에서 우리는 처리할 수 없는 에러라 판단되면 다시 던졌다. 프라미스에서도 유사한 작업을 구현해보자.

 

new Promise((resolve, reject) => {
  throw new Error("에러 발생!");
}).catch(function(error) { 
  if (error instanceof URIError) {
    // 에러 처리
  } else {
    alert("처리할 수 없는 에러");
    throw error; // 에러 다시 던지기
  }
}).then(function() {
  // 에러가 처리되면 실행
}).catch(error => { 
  alert(`알 수 없는 에러가 발생함: ${error}`);
});

 

 - 위에서 먼저 에러를 발생시켜 catch로 넘어갔다. 여기서 if로 처리 가능한 에러일 경우 처리하도록 하였고, else로 처리가 불가능하면 다시 던지도록 하였다.

 

 - 만약 에러가 처리된다면 then으로 넘어가게된다. 그러나 에러가 처리되지 못하면 다음 catch로 넘어간다.

 

 

 


 

 

참고

 

 

 

모던 자바스크립트 튜토리얼

 

모던 JavaScript 튜토리얼

 

ko.javascript.info

 

'Language > JavaScript' 카테고리의 다른 글

<ES6> ES2015(ES6) Features (2)  (0) 2021.08.01
<ES6> ES2015(ES6) Features (1)  (0) 2021.08.01
<자바스크립트> 프라미스  (0) 2021.04.21
<자바스크립트> 콜백  (0) 2021.04.19
<자바스크립트> try catch & 에러  (0) 2021.04.16