Language/JavaScript

프라미스 체이닝

nayoon030303 2021. 7. 15. 02:25

저번 글에 이어서 이번에도 프라미스에 대해서 공부해 보도록 하겠습니다.

프라미스 체이닝

스크립트를 불러오는 것과 같이 순차적으로 처리해야 하는 비동기 작업이 여러 개 있을 수 있습니다. 

만약 콜백만을 사용한다면 '콜백 지옥' 혹은 '멸망의 피라미드'가 만들어질 수 있습니다. 

더 자세히 콜백에 대해서 알고 싶다면 아래 사이트를 참고하세요

https://ko.javascript.info/callbacks

 

콜백

 

ko.javascript.info

 

만약 이럴떄 프라미스를 사용한다면 여러 가지 해결책을 만들 수 있습니다.

이번 시간에는 프라미스 체이닝(promise chainnig)을 이용한 비동기 처리에 대해서 알아보도록 하겠습니다. 

 

 

프라미스 체이닝이란?

 

프라미스 체이닝은 아래와 같이 생겼습니다.

new Promise(function(resolve,reject){

	setTimeout(()=>resolve((1),1000); 
    
}).then(function(result){

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

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

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

 

위 코드는 다음과 같은 순서로 실행됩니다.

  1. 1초 후 최초 프라미스가 이행됩니다. 
  2. 이후 첫 번째. then 핸들러가 호출됩니다. 
  3. 2에서 반환한 값이 다음. then 핸들러에 전달됩니다. 
  4. 이런 과정이 계속 이어집니다. 

result가 핸들러 체인에 따라 전달되므로, alert창엔 1,2,4가 순서대로 출력됩니다.

모던 자바스크립트 프라미스 체이닝 설명 사진

 

프라미스 체이닝이 가능한 이유

프라미스 체이닝이 가능한 이유는 promise.then을 호출하면 프라미스가 반환되기 때문입니다. 반환된 프라미스에는 당연히. then을 호출할 수 있습니다. 

또 핸들러가 값을 반환할 때 이 값이 프라미스의 result가 됩니다. 따라서 다음. then은 이 값을 이용해 호출됩니다. 

 

초보자가 주의해야 할 점

모던 자바스크립트에서 글을 읽던 도중 초보자들이 할 수 있는 실수에 대해서 알게 되었습니다. 

초보자는 프라미스 하나에. then을 여러 개 추가한 후, 이를 체이닝이라고 착각할 수 있습니다.

하지만 이는 체이닝이 아닙니다.

 

아래는 프라미스체 이닝이 아닌 예시입니다.

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

promise.then((result)=>{
	alert(result);
    return result*2;
});

promise.then((result)=>{
	alert(result);
    return result*2;
});

promise.then((result)=>{
	alert(result);
    return result*2;
});

위의 예시는 하나의 프라미스에 여러 핸들러들을 등록했습니다. 이 핸들러들은 result를 순차적으로 전달하지 않고 독립적으로 처리합니다. 동일한 프라미스에 등록된. then은 모두 동일한 결과를 받습니다. 따라서 위 예제를 실행하면 모두 1이 출력됩니다. 

 

 

프라미스 반환하기 

. then(handler)에 사용된 핸들러가 프라미스를 생성하거나 반환하는 경우도 있습니다. 

이 경우에는 이어지는 핸들러는 프라미스가 처리될 때까지 기다린다음 처리가 완료되면 실행됩니다. 

 

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

위 예시는 첫 번째. then은 1을 출력하고 1초 있다가 new Promise를 반환합니다. 두 번째. then은 2를 출력하고 동일한 과정이 반복됩니다. alert창에는 1,2,4가 차례대로 출력됩니다. 하지만 1과 2 사이 2와 4 사이에 1초의 딜레이가 생깁니다. 

 

이렇게 핸들러 안에서 프라미스를 반환하는 것도 비동기 작업 체이닝을 가능하게 해 줍니다. 

 

 

fetch와 체이닝 함께 응용하기 

fetch함수를 사용해 원격 서버에서 사용자 정보를 가져올 수 있습니다.

fetch함수에 대해서 자세히 모른다면 아래 사이트를 참고하시기 바랍니다.

https://ko.javascript.info/fetch

 

fetch

 

ko.javascript.info

 

fetch함수를 이용해 정보를 가져오는 데에 있어서 시간이 필요합니다. 응답 전체가 완전히 다운로드되기 전에 프라미스가 실행됩니다. 

응답이 완전히 종료된 다음 응답 전체를 읽으려면. then메서드를 사용해야 합니다. 

 

 

아래 코드는 모던 자바스크립트에 있는 튜토리얼 예제입니다. 

API를 이용해 정보를 받아 본 적이 있다면 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); // (*)
  });

 

위 코드는 주석에 적은 대로 잘 동작합니다. 하지만 (*)로 표시한 줄을 본다면 만약 작업을 추가하는 경우 지금으로선 방법이 없습니다. 체인을 확장할 수 있도록 만들려면 아바타가 사라질 때 이행 프라미스를 반환해 줘야 합니다. 

 

아래는 체인을 확장한 코드입니다. 

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(`Finished showing ${githubUser.name}`));

 

비동기 동작은 항상 프라미스를 반환하도록 작성하는 것이 좋습니다. 지금은 체인을 확장할 계획이 없더라도 이렇게 구현해 놓으면 나중에 체인을 확장할 때 편리하게 체인을 확장할 수 있습니다. 

 

 

요약!

. then 또는. catch,. finally의 핸들러가 프라미스를 반환하면, 나머지 체인은 프라미스가 처리될 때까지 대기합니다. 처리가 완료된다면 프라미스의 result 또는 error가 다음 체인으로 전달됩니다.