본문 바로가기

IT/Coding Note

자바스크립트 프로미스 - ES6 비동기처리

프로미스는 이전 작업이 완료될 때까지 다음 작업을 연기 시키거나, 작업실패를 대응할 수 있는 비교적 새로운 자바스크립트 기능이다. Javascript promise는 비동기 작업 순서가 동기식으로 정확하게 작동되게 도움을 준다.

 

1) Promise란?

 ES6에서 도입된 프로미스는 콜백이 지닌 단점을 보완하며 비동기처리를 동기처럼 작동하게 해준다. 

let img = document.querySelector('.img');
const imageShow = () => {
fetch('./img/codehacking.jpg')
      .then(response => response.blob()) 
      .then(myBlob => { 
               let objectURL = URL.createObjectURL(myBlob);
               let image = document.createElement('img');
               image.src = objectURL;   
               image.style.width="55%";
               img.style.display = "block"; 
               img.innerHTML = '';   
               img.append(image); })
        .catch(e => { 
               console.log('problem with fetch operation: ' + e.message);
               });
const imageSet = () => {
             img.style.display = "none"; }

 

Promise가 생성되면 그 상태는 성공도 실패도 아닌 pending상태라고 부른다.
Promise 결과가 반환되면 결과에 상관 없이 resolved 상태라고 부른다.
성공적으로 처리된 Promise는 fulfilled상태이다. 
이 상태가 되면 Promise 체인의 다음 .then() 블럭에서 사용할 수 있는 값을 반환한다.
그리고 .then() 블럭 내부의 executor 함수에 Promise에서 반환된 값이 파라미터로 전달된다.
실패한 Promise는 rejected상태이다. 
이때 어떤 이유(reason) 때문에 Promise가 rejected 됐는지를 나타내는 에러 메시지를 포함한 결과가 반환된다. 
Promise 체이닝의 제일 마지막 .catch()에서 상세한 에러 메시지를 확인할 수 있다. 

 

2) Promise.all([ ])

배열의 모든 Promise가 fulfil 이면, .then() 블럭의 executor 함수로의 매개변수로 Promise 결과의 배열을 전달한다. 
Promise.all() 의 Promise의 배열 중 하나라도 reject라면, 전체 결과가 reject가 된다.

Promise.all([a, b, c]).then(values => { ... });

 

let sample = document.querySelector('.sample');   
function fetchAndDecode(url, type) {
    return fetch(url).then(response => {
        if (type === 'blob') {
            return response.blob();
        } else if (type === 'text') {
            return response.text();
        }
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message);
});
}

let catAni = fetchAndDecode('./img/cat-ani.png', 'blob');
let catEye = fetchAndDecode('./img/cat-eye.png', 'blob');
let description = fetchAndDecode('./img/sample.txt', 'text');

let promiseAll = () => {
Promise.all([catAni, catEye, description]).then(values => {

// console.log(values);
// 프로미스를 반환한 결과값이 vlaues의 배열에 담겨있다.
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];

// 변수에 담긴 프로미스의 값을 출력하기
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.style.width="30%";
image2.style.width="30%";
image1.src = objectURL1;
image2.src = objectURL2;

// 일단 기존 화면 지우기 
sample.style.display ="block";
sample.innerHTML = '';

sample.append(image1);
sample.append(image2);

// 텍스트 담기
let para = document.createElement('p');
para.textContent = descText;
sample.append(para);

});
}

let btnShow = document.querySelector('.btnShow');
let btnDel = document.querySelector('.btnDel');

btnShow.addEventListener('click', promiseAll);
btnDel.addEventListener('click', () => {
    sample.style.display ="none"; 
});

 

3) promise.all() 업그레이드 코드

async/await는 promises의 상위에 만들어져 있기 때문에 Promise의 모든 기능을 사용할 수 있다. 따라서 Promise.all() 앞에 async키워드를 사용하여 동기식 코드처럼 작성할 수 있다.

기존 then()메소드 활용 : Promise.all([a, b, c]).then(values => { ... });
await키워드를 활용한 방식 = > let values = await Promise.all([a, b, c]);

 

let sample = document.querySelector('.sample');   

async function fetchAndDecode(url, type) {
   let response = await fetch(url);
   let content;
   if(!response.ok){
       throw new Error(`HTTP 에러 발생! ${response.status}`);
   } else {
     if(type === 'blob') {
         content = await response.blob();
     }else if(type === 'text') {
         content = await response.text();
     }
   }
  return content;
}

async function displayContent(){
    let catAni = fetchAndDecode('./img/cat-ani.png', 'blob');
    let catEye = fetchAndDecode('./img/cat-eye.png', 'blob');
    let description = fetchAndDecode('./img/sample.txt', 'text');
    
    let values = await Promise.all([catAni, catEye, description]);
    // 프로미스를 반환한 결과값이 vlaues의 배열에 담겨있다.
    let objectURL1 = URL.createObjectURL(values[0]);
    let objectURL2 = URL.createObjectURL(values[1]);
    let descText = values[2];

    // 변수에 담긴 프로미스의 값을 출력하기
    let image1 = document.createElement('img');
    let image2 = document.createElement('img');
    image1.style.width="30%";
    image2.style.width="30%";
    image1.src = objectURL1;
    image2.src = objectURL2;

    // 일단 기존 화면 지우기 (개인적으로 추가함)
    sample.style.display ="block";
    sample.innerHTML = '';

    sample.append(image1);
    sample.append(image2);

    // 텍스트 담기
    let para = document.createElement('p');
    para.textContent = descText;
    sample.append(para);
}

displayContent().catch( (e) => {
    console.log(e)
});
    

let btnShow = document.querySelector('.btnShow');
let btnDel = document.querySelector('.btnDel');

btnShow.addEventListener('click', displayContent);
btnDel.addEventListener('click', () => {
    sample.style.display ="none"; 
});

 

Async/await 는 코드를 마치 동기식 코드처럼 보이게 한다.
함수 블럭에 여러 개의 await 키워드를 사용하면 Promise가 fulfilled되기 전 까지 다음 await를 차단한다.
그 동안 다른 태스크는 계속 실행이 되지만 정의한 함수 내에서는 동기적으로 작동한다.

에러를 다루기 위해 .catch() 블럭을 displayContent() 함수를 호출하는 곳에 추가한다.
이렇게 하면 두 함수에서 발생하는 에러를 처리할 수 있다.
fetchAndDecode() 함수는 throw new Error를 통해 에러가 발생하면 catch(e)로 자동으로 이동한다.
displayContent() 함수는 .catch(e)를 체인으로 연결했다.