티스토리 뷰

11.3 Promises chaining

프라미스는 주로 체이닝을 해서 사용한다. 아래와 같이 말이다. 프라미스의 .then은 프라미스를 반환한다. 따라서 체이닝이 가능하다. 이 때 반환되는 result 값이 다음 then으로 전달 되므로 결과도 그에 맞게 반환된다.

new Promise(function(resolve, reject) {

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

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

위 예시와 아래 예시는 분명히 다르다. 아래 예시는 여러개의 then을 호출한 것이지 체이닝이 아니다.

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

Returning promises

프라미스 체이닝 중에 새로운 프라미스를 반환할 수도 있다.

new Promise(function(resolve, reject) {

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

}).then(function(result) {

  alert(result); // 1

  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) { // (**)

  alert(result); // 2

  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });

}).then(function(result) {

  alert(result); // 4

});

위 코드를 보면 처음 에시와는 다르게 두번째, 세번째 프라미스가 반환될때, 1초의 딜레이가 발생하게 되어 있다.

Example: loadScript

이전 챕터에서 정의한 loadScript 예시를 다시보자.

loadScript("/article/promise-chaining/one.js")
  .then(function(script) {
    return loadScript("/article/promise-chaining/two.js");
  })
  .then(function(script) {
    return loadScript("/article/promise-chaining/three.js");
  })
  .then(function(script) {
    // 불러온 스크립트 안에 정의된 함수를 호출해
    // 실제로 스크립트들이 정상적으로 로드되었는지 확인합니다.
    one();
    two();
    three();
  });

화살표 함수를 통해 코드를 좀 더 간결하게 할 수도 있겠다.

loadScript("/article/promise-chaining/one.js")
  .then(script => loadScript("/article/promise-chaining/two.js"))
  .then(script => loadScript("/article/promise-chaining/three.js"))
  .then(script => {
    // 스크립트를 정상적으로 불러왔기 때문에 스크립트 내의 함수를 호출할 수 있습니다.
    one();
    two();
    three();
  });

thenable

핸들러는 프라미스가 아닌 thenable이라 불리는 객체를 반환하기도 한다. .then이 가능한 객체는 모두 thenable 객체라 하고, 객체도 프라미스와 같은 방식으로 처리된다. 

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { 네이티브 코드 }
    // 1초 후 this.num*2와 함께 이행됨
    setTimeout(() => resolve(this.num * 2), 1000); // (**)
  }
}

new Promise(resolve => resolve(1))
  .then(result => {
    return new Thenable(result); // (*)
  })
  .then(alert); // 1000밀리 초 후 2를 보여줌

이러한 객체를 이용해 Promise를 상속받지 않고도 체이닝을 사용할 수 있다.

Bigger example: fetch

비동기 동작은 항상 프라미스를 반환하도록하는 것이 좋다. 아래 예시를 주의깊게 살펴보자.

// user.json에 요청을 보냅니다.
fetch('/article/promise-chaining/user.json')
  // 응답받은 내용을 json으로 불러옵니다.
  .then(response => response.json())
  // GitHub에 요청을 보냅니다.
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  // 응답받은 내용을 json 형태로 불러옵니다.
  .then(response => response.json())
  // 3초간 아바타 이미지(githubUser.avatar_url)를 보여줍니다.
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

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

(*)줄에서 img.remoce() 후 뭔가 또 다른 작업을 하고 싶으면 어떻게 해야할까?

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise(function(resolve, reject) { // (*)
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser); // (**)
    }, 3000);
  }))
  // 3초 후 동작함
  .then(githubUser => alert(`${githubUser.name}의 이미지를 성공적으로 출력하였습니다.`));

setTimeout에서 resolve했을 때 처리되는 새로운 프라미스를 반환하는 것이다. 이렇게 다음 체이닝으로 넘어가서 또다른 동작을 수행할 수 있다. 좀 더 간결해진 예시

function loadJson(url) {
  return fetch(url)
    .then(response => response.json());
}

function loadGithubUser(name) {
  return fetch(`https://api.github.com/users/${name}`)
    .then(response => response.json());
}

function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

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

// 함수를 이용하여 다시 동일 작업 수행
loadJson('/article/promise-chaining/user.json')
  .then(user => loadGithubUser(user.name))
  .then(showAvatar)
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));
  // ...

그런데 어차피 then을 하면 프라미스가 반환되는 것 아닌가? 헷갈린다... 추가적으로 더 살펴봐야겠다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday