본문 바로가기

3. HTML | CSS | Javascript

4/12(수) IT K-DT(30일차) / 31.프로미스(Promise)~32.옵셔널체이닝(Optional chaining)


31. 프로미스(promise)

    31-1. 정의

    31-2. 장점

    31-3. 상태의 유형

    31-4. 메소드(method)

 

32. 옵셔널 체이닝(Optional Chaining)

 

TIP


31. 프로미스(promise)

31-1. 정의

Javascript 비동기 처리에 사용되는 객체.
비동기적으로 처리되는 작업의 결과를 나중에 처리하기 위해 사용.
주로 서버에서 받아온 data를 화면에 표시할 때 사용.

31-2. 장점

비동기 처리 시점을 명확히 표현할 수 있음.
연속된 비동기 처리 작업을 유연하게 수정, 삭제, 추가하기가 편함.

비동기 처리 작업 시 성공/실패 등의 상태 정보를 갖게 됨.
코드의 유지 보수성이 증가.

31-3. 상태의 유형

매개변수로 사용.
    Pending: 작업이 아직 완료되지 않은 상태.
    Resolve: 작업이 성공적으로 완료된 상태.
    Reject: 작업이 실패한 상태.

 

31-4. 메소드(method)

then(): 작업이 성공적으로 완료된 경우에 호출.

catch(): 작업이 실패한 경우에 호출. 
finally(): 최종적으로 처리할 일을 수행할 경우에 호출.

 

then() 메소드와 catch() 메소드는 '메소드체인'을 이용하여 여러 개의 작업을 연속적으로 처리 가능.




        const 프로미스객체 = () => new Promise ( ( resolve , reject ) => {

        }); 

비동기 처리
작업이 완료될 때까지 기다리지 않고 다른 작업을 수행하는 방식. 
비동기 처리는 대개 네트워크 요청, 파일 처리, 타이머와 같이 시간이 오래 걸리는 작업을 수행할 때 사용. 
이를 통해 사용자 경험을 향상시키고, 애플리케이션의 성능을 향상시킬 수 있음.

공부한 내용 중 비동기 처리의 예시:
    1. setTimeout 함수: 지정된 시간이 지난 후에 코드를 실행. 애니메이션 및 타이머와 같은 기능을 구현할 수 있음.
    2. 이벤트 핸들러: 클릭, 마우스 오버, 키보드 입력 등과 같은 사용자 동작을 비동기적으로 처리할 수 있음. 
        이를 통해 사용자는 페이지와 상호작용하면서 다른 작업도 동시에 수행할 수 있음.
    3. Promise: Promise 객체를 사용하여 비동기적인 작업을 처리할 수 있음. 
        Promise는 작업이 완료될 때까지 기다리지 않고, 다른 작업을 수행할 수 있도록 도와줌.

 

예) console에 '3초후 알림이 뜨는 타이머'를 만드려는 경우.


  function runInDelay(seconds){ 
            
            return new Promise((resolve, reject) => {
            
                if (!seconds || seconds<0) { 
                reject (new Error('seconds가 0보다 작음')); // seconds가 없거나, 0보다 작다면 Error가 발생.
                }
                setTimeout(resolve, seconds*1000); // ( 1000 = 1초 )
            });
  }

        runInDelay(3)
            .then(()=>console.log('타이머가 완료되었습니다.')) // 메소드체인. 가독성을 위해 온점(.)을 다음 줄에 내려서 사용.
            .catch(console.error)
            .finally(()=>console.log('모든 작업이 끝났습니다.'));

 

예) console에 '🐤=>🐔=> 🥚=>🍳'가 차례대로 출력되는 경우


        function fetchEgg(chicken){
            return Promise.resolve(`${chicken} => 🥚`); // Promise가 성공하면 실행
        } 

        function fryEgg(egg){
            return Promise.resolve(`${egg}=>🍳`);  // Promise가 성공하면 실행
        }

        function getChicken(){
            return Promise.resolve(`🐤=>🐔`);  // Promise가 성공하면 실행

        }

        // 비동기 처리 실행 예정.
        getChicken()
        .then((chicken) => {
            return fetchEgg(chicken);
        })
        .then((egg)=>fryEgg(egg))
        .then((friedegg) =>console.log(friedegg))
        .catch(()=>'🐥');

        // 바로 위의 코드를 축약형으로 사용할 수 있는 방법.
        getChicken()
        .then(fetchEgg)
        .then(fryEgg)
        .then(console.log)
        .catch(()=>'🐥');

 

예) 1초 후에 '🍌', 3초 후에 '🍎', Error 발생시 '오렌지는 다팔림'을 출력하는 각각의 function을 생성.

      이후, '🍌' 와 '🍎'를 동시에 Array로 출력해 console에 저장. (동시에 출력 시 총 4초+a정도의 시간이 소요)


        function getBanana(){
            return new Promise((resolve)=>{
                setTimeout(()=>{
                    resolve('🍌');
                }, 1000);
            });
        }

        function getApple(){
            return new Promise((resolve)=>{
                setTimeout(()=>{
                    resolve('🍎');
                }, 3000);
            });
        }

        function getOrange(){
            return Promise.reject( new Error('오렌지는 다팔림'));
        }

        getBanana()
            .then((banana)=>
            getApple()
                .then((apple)=>[banana, apple]))
                .then(console.log); // banana, apple을 동시에 array로 출력

     

 

 

Promise.all
병렬적으로 한 번에 모든 Promise를 실행하는 기능.
여러 개의 console을 출력 시 우선적으로 Promise.all이 출력.


 Promise.all([getBanana(), getApple()])
     .then((fruits)=>console.log('Promise.all: ', fruits));

예) Promise.all에 getOrange()를 추가.


  Promise.all([getBanana(), getApple(), getOrange()])
     .then((fruits)=>console.log('Error: ', fruits))
     .catch(console.log);

'Error: 오렌지는 다팔림 at getOrange (3.프로미스3.html:30:35) at 3.프로미스3.html:49:51' 
라는 Error내용이 먼저 출력된 후, 나머지가 순차적으로 출력됨.
하나의 요소가 Error일 경우에도 전체적인 Error가 발생.

 


Promise.allSettled
Error가 발생하더라도, 모든 promise들의 결과를 반환.


Promise.allSettled([getBanana(), getApple(), getOrange()])
      .then((fruits)=>console.log('Promise.allSettled: ', fruits))
      .catch(console.log);

 


Promise.race
주어진 Promise 중에서 가장 빨리 수행되는 것을 출력.


Promise.race([getBanana(), getApple()])
     .then((fruit) => console.log('Promise.race: ', fruit)); // fruits가 아닌, fruit

 

32. 옵셔널 체이닝(Optional Chaining)

비교적 최근에 추가된 문법. (ECMA Script 11버전에 추가)
null 또는 undefined를 확인할 때 쓰이는 연산자.
객체의 중첩된 속성 값에 접근할 때 발생할 수 있는 오류를 방지하기 위해 사용.
옵셔널 체이닝 연산자( ?. )는 왼쪽 피연산자 값이 null 또는 undefined인 경우, 

바로 undefined를 반환하며 오류를 방지.
코드의 안정성을 높일 수 있음.

 

예) 옵셔널  체이닝(Optional Chaining)을 사용하지 않았을 때 오류가 발생하는 경우.


const user = {
  name: 'John',
  address: {
    city: 'New York',
    zipcode: 10001
  }
};

console.log(user.address.country.code); // TypeError: Cannot read property 'code' of undefined

user 객체에는 address 프로퍼티가 존재하고, address 객체에는 country 프로퍼티가 존재하지 않음.

따라서 user.address.country.code와 같은 코드는 TypeError를 발생.

 

예) 위의 오류에서 옵셔널 체이닝(Optional Chaining)을 사용하여 Error 방지를 하는 경우.


const user = {
  name: 'John',
  address: {
    city: 'New York',
    zipcode: 10001
  }
};

console.log(user?.address?.country?.code); // undefined

Error가 발생하지 않지만, 해당 프로퍼티의 값이 undefined로 평가됨.

 

논리 연산자(&&, ||)를 이용하는 경우


        const obj1 = {name:'김사과'};
        const obj2 = {name:'반하나', lover:'이메론'};

        if (obj1 || obj2 ){
            console.log('둘 다 또는 둘 중에 하나가 true'); // 둘 다 또는 둘 중에 하나가 true
        }

        let result = obj1  & &  obj2 ; // obj1과 obj2 모두 값이 존재하여 true. and 연산이므로 최종값은 true.
        console.log(result); // lover: "이메론"   name: "반하나"

        result = obj1  | |  obj2 ;  // obj1과 obj2 모두 값이 존재하여 true. or 연산이므로 최종값은 true.
        console.log(result); // name: "김사과"
       // obj2는 코드에서 선언만 되고 실제로 사용되지 않았기 때문에 result 변수에 할당되지 않음.



        function changeLover(obj){        
            if(!obj.lover){
                throw new Error('애인이 없어'); // obj에 lover라는 필드값이 없다면, '애인이 없어'라는 Error를 출력.
            }
            obj.lover = '애인이 바뀜'; // 반대로 lover라는 필드값이 있다면, '애인이 바뀜'을 출력
        }

        function makeNewLover(obj){
            if(obj.lover){
                throw new Error('애인이 있어'); // obj에 lover라는 필드값이 있다면, '애인이 있어'라는 Error를 출력.
            }
            obj.lover = '새로운 애인이 생김';
        }

        obj1.lover & & changeLover(obj1 );        // obj1.lover의 값이 없으므로 false. → changeLover(obj1)이 사용될 필요가 없음.
        console.log(obj1); // name: "김사과"

        obj2.lover & & changeLover(obj2 );       // obj2.lover:'이메론'. true. changeLover(obj2)에서 '애인이 바뀜'으로 변경.
        console.log(obj2); // lover: "애인이 바뀜"    name: "반하나" 


 

null 또는 undefined가 이용되는 경우


 let item = {price:1000};
 const price = item && item .price;
      // item 객체를 확인한 후 true라면, 순차적으로 item.price를 확인.
         and 연산자를 실행했을 때 결과적으로 둘 다 값이 있어서 true → price에서 1000이라는 값을 출력.

 console.log(price); // 1000


 let item2 = {price:1000};
 const price2 = item2 ?. price; // item2 && item2.price; 와 동일한 코드.

 console.log(price2); // 1000

 

Javascript에 data가 없을때의 기본값(default parameter)은 undefined이다.

예) 

print()값에 data가 없으면 undefined 대신에 'hello'가 출력.

        
 function print(message){            
   const text = message || 'hello';
   console.log(text); // text를 출력 시 print 안의 'message'가 출력되거나, 'hello'가 출력됨.
 }


print('안녕');           // 안녕

print();                    // hello

print(undefined);    // hello

print(null);              // hello

print('');                  // hello


 
 let num = 0;

 console.log(num ||'-1'); // -1. num = 0이므로 false. '-1'은 true. → or연산자에 따라 true인 '-1'이 출력.
 console.log(num ?? '-1'); // 0. and연산자에 따라 0이 출력.

TIP

* day7에 배우는 내용들은 직접적으로 data를 가져다 뿌릴 수 있게 만드는, 

프론트엔드 개발자의 핵심적인 부분이므로 중요함.

 

 

* 주석
//             한줄
/*   ...   */ 여러줄
/**  ...  */ JSDoc 주석 (실제로 실무에서 많이 사용하는 주석처리방법.)

    JSDoc 사용법:
    @param {*} path 문자
    @returns 파일객체

    ...