Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

도도의 IT이야기

콜백 vs promise 본문

IT/자바스크립트

콜백 vs promise

도도버드 2020. 5. 16. 00:26

콜백을 대체할 수 있는 프로미스를 대해서 알아보거다.

 

하지만 그전에 콜백에 대해서 한번 복습해보자.

 

콜백은 다른 함수(higher-order function)의 파라미터로 전달되는 함수로써 그 higher-order이 원할때 호출될 수 있다.

 

function add(a,b,print){
  let sum = a+b;
  print(sum);
}

function result(res){
   console.log(res);//3
}

add(1,2,result)

 이런 식으로 우리는 result 함수를 add의 인자로 보내 사용할 수 있다.

 

물론 위의 함수를 이런식으로도 쓸수있다.

function add(a,b){
  return a+b;
}

function result(res){
   console.log(res);//3
}

let x = add(1,2);
let y = result(x);

 

하지만 이렇게 따로따로 쓰는거 보다 encapsulation(함수안에서 사용할 것은 함수안에 있는게 좋음) 때문에 콜백을 활용하는 것이 좋다.

 

 

콜백함수는 클로져다

function higher(a,b){
   let sum = a+b;
   callback(function(){//안에서 정의됨
      console.log(sum);//3
   })
}

function callback(cb){
   cb();//밖에서 호출됨
}

higher(1,2);//3

 이번에는 콜백의 정의를 안에서 해보았다(위의 예제들은 전부 ).

 

보다시피 밖에서 호출됬지만 자신이 선언됨 곳의 렉시컬 스코프에대한 접근 권한이 있기 때문에 클로져라고 할 수 있다.

 

또한 

 

클로져는 렉시컬 스코프에 접근할 수만 있을뿐 문맥까지 가지고 올 수 없다. 즉 this의 사용에 주의해야한다. 예를 들자면

let obj = {
name:'dodo',
age:16,
setName:function(name){
   this.name = name;
}
}

function higher(name,cb){//메소드를 콜백으로 사용할시 this를 주의해야함
  cb(name);//콜백은 클조져로써 name과 age의 값을 접근할 수 있지만 문맥까지 가져올 수 없음. 즉 this가 obj을 가리키지 않음
}

higher('dede',obj.setName);
console.log(obj.name);//dodo

 위의 예제는 객체안의 메소드를 콜백함수로 사용했다. 콜백함수는 this를 사용하는데 콜백함수가 호출된 위치가 obj의 안이 아닌 higher함수이기 때문에 this는 window를 가리킨다. 그래서 call을 이용해 binding을 해줘야함.

let obj = {
name:'dodo',
age:16,
setName:function(name){
   this.name = name;
}
}

function higher(name,cb){//메소드를 콜백으로 사용할시 this를 주의해야함
  cb.call(obj,name);//이렇게 문맥을 obj로 고정시켜줘야함
}

higher('dede',obj.setName);
console.log(obj.name);//dede name속성의 값이 잘 바뀜

 

콜백의 장점과 단점

 

이벤트에서의 장점

 

지금까지 콜백의 동기적 사용에 대해 알아봤다. 하지만 콜백의 진가는 비동기적 사용에서 나온다.

let button = document.querySelector('#btn');

button.addEventListener('click',callback);

function callback(){//코드}

원래 함수는 호출되면 바로바로 실행되는데, 여기서는 콜백함수은 web api로 사라지고 나머지 코드들이 먼저 실행된다. 그리고 이벤트가 발생하면 call stack이 다 비워졌을때 호출됨.

 

AJAX에서의 장점

 

이벤트말고도 AJAX에서도 효과적이다. AJAX는 나중에 자세히 다룰거기 떄문에 setTimeout으로 흉내를 내보겠다.

function getPicture(){//사진 가져오는 AJAX라고 해보자
   setTimeout(()=>{
      console.log('사진 가져왔다!');//나중에 실행됨
   },2000)
}

getPicture();
console.log('다른 코드 진행중');//먼저 실행됨

 

위의 getPicture함수가 사진을 가져올때 동안 밑에 코드들이 먼저 실행된다. 그리고 사진을 가져오는게 완료되면 callback queue에서 call stack이 빌때 까지 기다렸다가 callback queue에 도착한 차례대로 call stack에서 실행됨.

 

하지만 AJAX를 사용할때 기억해야 되는 점이 있다.

 

let list = [{name:'dodo',age:12},{name:'elisa',age:24}];

function getList(){
   setTimeout(()=>{
      console.log(list);//나중에 실행됨
   },1000)
}
function finish(){
  console.log('다 가져왔음');//먼저 실행됨
}

getList();
finish();

위와 같은 코드가 있다고 하자.

 

우리는 getList를 먼저 호출하고 finish를 호출했지만 getList는 비동기적인 AJAX(흉내)이기 때문에 "다 가져왔음"이 먼저 실행된다.

 

근데 우리는 finsh함수를 getLIst함수가 호출된 다음에 호출하고 싶다면 어떻게 해야할까?

 

이떄 우리는 콜백 함수를 사용할 수 있다.

 

중요 포인트: 콜백 함수로 AJAX의 결과를 활용할 수 있다.

 

AJAX가 가져온 결과를 활용하거나 AJAX다음에 무슨 로직을 실행하고 싶으면 콜백 함수를 사용하면 된다.

 

위의 코드를 바꿔보자

 

let list = [{name:'dodo',age:12},{name:'elisa',age:24}];

function getList(callback){
   setTimeout(()=>{
      console.log(list);//먼저 실행됨
      callback();즉 1000ms를 같이 기다렸다 순차적으로 나중에 실행됨
   },1000)
}
function finish(){
  console.log('다 가져왔음');//먼저 실행됨
}

getList(finish);

 

finish 콜백함수를 인자로 보내 list가 도착한뒤, 실행하였다.

 

이렇게 어떠한 동기적 로직을 AJAX다음에 실행할 수 있다.

 

그럼 이런 코드를 보자

let list = [{name:'dodo',age:12},{name:'elisa',age:24}];

function getList(callback){
   setTimeout(()=>{
      console.log(list);//먼저 실행됨
   },1000)
}
function addList(){
   setTimeout(()=>{
      list.push({name:'chris',age:28});//나중에 실행됨
   },2000);
}

addList();
getList();

이제 두가지 비동기적 AJAX가 있다고 가정하자.

우리는 addList함수를 먼저 호출하고 getList를 나중에 호출했다.

하지만 우리의 결과에는 이상하게도 addList로 추가한 object가 나오지 않는다. 왜그럴까?

왜냐하면 getList는 1초가 걸리는데 addList는 2초가 걸리기 때문이다. 즉 AJAX로 먼저 가져온 정보가 우선 실행되는 것이다.

 

그럼 getList를 addList가 실행된 다음에 하고 싶다면 아까와 같이 콜백을 사용하면 된다.

let list = [{name:'dodo',age:12},{name:'elisa',age:24}];

function getList(){
   setTimeout(()=>{
      console.log(list);
   },1000)
}
function addList(callback){
   setTimeout(()=>{
      list.push({name:'chris',age:28});//먼저 실행
      callback()//다음에 실행
   },2000);
}

addList(getList);

 

이러하게 추가한 객체가 포함된 배열을 리턴한다.

 

그래서 여기서 우리는 어떠한 콜백함수를 비동기적 AJAX request가 끝난뒤 호출 할 수 있다. 는 것을 기억해야한다.

 

하지만 위의 코드는 콜백의 궁극적인 장점을 충분히 설명하지 않는다. 어떠한 함수를 AJAX가 끝난뒤 실행했을 뿐.

그러니 밑의 코드를 보자

function getData(url,callback){
   //url에서 {name:'Chris'} 라는 데이터를 가져옴
   callback({name:'Chris'});//callback의 인자로 가져온 데이터를 전달함
}

getData('www.example.com/123',function(data){
   console.log(data);//{name:'Chris'}
})

콜백함수의 엄청난 장점은 이러하다. 어떠한 콜백함수를 AJAX request가 끝난뒤 호출하는 것 뿐만이 아닌. 그 콜백함수에 AJAX의 response를 인자로 넣어서 프로그래머가 그 response를 자유롭게 사용 가능하다는 점이다. 

 

우리는 가져온 데이터를 console.log밖에 안했지만 JS DOM을 이용해 HTML에 정보를 추가하는 등 여러가지 일을 할 수 있다.

 

특히 우리가 할 수 있는 여러가지 일들중 이런게 있다. 전달 받은 AJAX response를 인자에 넣어 또다른 함수를 호출할 수 있다. 말로 하니 여려우니 위의 코드에 라인을 추가하면서 설명하겠다.

function getData(url,callback){
   //url에서 {name:'Chris'} 라는 데이터를 가져옴
   callback({name:'Chris'},false);//callback의 인자로 가져온 데이터를 전달함, 에러여부도 전달
}

function validateName(name,callback){
   validate(name);//여기서 이름의 validation이 체크됨
   callback(name, false);
}

getData('www.example.com/123',function(data,err){
   if(!err){
   validateName(data.name,function(name,err){
      if(!err){
         console.log(name);
      }
   });
   }
})

 

우리는 getData에서 가져온 데이터, data.name을 validateName함수의 인자로 넣었다. validateName함수는 파라미터로 받은 이름을 검증하고 검증결과와 이름을 콜백함수의 인자로 넣는다. 그렇게 검증결과가 true면 이름을 console.log하는 코드를 만들어 보았다. 

 

이렇게 콜백의 장점을 보았다.

1. 비동기적 메소드 (event, AJAX)에 사용가능

2. AJAX로 서버 정보를 가져온 뒤, 어떠한 로직을 콜백을 통해 호출 가능하다.

3. 서버 정보(response)를 콜백함수의 인자로 전달해 프로그래머가 그것을 활용할 수 있다.

 

콜백의 단점

장점이 있으면 단점도 있다.

 

콜백의 단점에 대해서 알아보자.

 

function getUser(id,password,callback){
   setTimeout(()=>{
     //아이디하고 패스워드가 맞는지 확인
     console.log('we got your data!')     
     callback({username:'dodoBird'},false);//유저 이름과 아이디 패스워드가 맞는지 넣음 
   },2000);
}

function getVideo(username,callback){
   setTimeout(()=>{
      console.log('your videos are here~');
      callback(['titians','Harry Potter','Narnia'],false);//사용자의 플레이리스트와 에러여부
   },1000);
}

function getDescription(video,callback){
  setTimeout(()=>{
      console.log('finding description for: '+video);
      callback('this video is about blahblah',false);//비디오들의 정보 와 에러여부
  },1500)
  
}

getUser('abc',123123,function(response,err){//이름
   if(!err){
      getVideo(response.username,function(playList,err){
         if(!err){
            console.log(playList);
            getDescription(playList[0],function(description,err){
              if(!err){
                console.log(description);
                getDescription(playList[1],function(description,err){//보이다 시피 자신의 위의 scope에 있는 variable에 access가 가능하다(playList)
                  if(!err){
                    console.log(description);
                      
                  }
                })
              }
            })
         }
      });
   }
});

우선 빤히 보이는 문제 전에 코드를 분석해보자. 우선 위의 함수들이 setTimeout으로 인해 걸리는 시간이 제각각이지만 콜백을 사용하므로써 순차적으로 실행되었다.

 

잊어버릴까봐 다시 말하자면 getUser의 인자에 아이디와 비밀번호을 넣는다 그리고 프로세스가 처리된 후, 마지막 인자인 callback함수에 데이터가 담겨져 온다 그래서 우리는 그 데이터를 활용해 다른 함수들을 invoke했다.

 

이제 문제점 대해서 얘기해보자. 너무 복잡하고 보기 싫다. 특히 저 삼각형은 triangle of doom이라고도 불리는 콜백 지옥의 현상이다. 코드에 이상이 있으면 수정하기도 너무어렵고 debuggin도 너무 힘들다. 그래서 이 문제를 해결하기 위한 방법을 이제부터 설명하겠다.

 

1. 동기 함수를 사용한다

>이게 무슨뜻이냐? 우리는 지금까지 setTimeout을 사용해 비동기 AJAX를 흉내했다. 하지만 AJAX내에서 비동기식 말고 동기식으로 각각 함수들을 처리하는 방법이있다. 하지만 동기식으로 처리하면 시간도 오래걸리고 굉장히 비효율적이기에 추천하지 않음

 

2.콜백함수를 분리

우리는 지금까지 anonymous function /익명함수를 사용해 콜백함수를 선언했다. 하지만 익명함수에게 이름을 주고 분리할 수 있다.

getUser('abc',123123,step1);

function step1(response,err){//이름
   if(!err){
      getVideo(response.username,step2);
      }
   }
      
function step2(playList,err){
    if(!err){
        console.log(playList);
        getDescription(playList[0],step3)
        }
     }
            
function step3(description,err){
     if(!err){
         console.log(description);
         getDescription(playList[1],step4);//위의 playList에 접근을 못함. 에러뜸
         }
    }
           
function step4(description,err){
    if(!err){
    console.log(description);
    }
}
                      
               

 

이런 식으로 다시 쓸 수 있지만 이상적이진 않다.

그리고 이 코드는 전 단계에서 받은 데이터를 다음단계에서 사용 불가능이기에 단점이있다. 그래서 글로벌 변수를 이용해야하는데 그것도 너무 안좋고 쓸데없이 이름짓기도 힘들다.

 

3. PROMISE 프로미스

지금부터 최근에 나온 프로미스대해서 알아보자

 

우선 프로미스가 무엇인가?

 

프로미스는 자바스크립트의 프로미스 객체를 생성하는 빌트인 생성자로써 주목적은 이러하다.

어떠한 비동기적 프로세스(AJAX로 데이터를 가져오는 등)를 수행하는 "executer 코드"와 실행된 코드의 결과를 접근하는 "consumption 코드"를 연결해주는 역활을 한다. 

 

쉽게 말하면 비동기적 콜백의 대체자이다.

 

말로 하면 어려우니 예제를 보자

let p = new Promise(function(resolve, reject){//executer 함수
   //서버에서 아이디를 가져오는 프로세스가 실행됨
      let error = false;
      if(!error){
      resolve({id:'dodoBird'});
      }else{
      reject(new Error("can't find id"));
      }
})

p.then(function(data){console.log(data.id)});

    

우선 우리는 new키워드를 사용해 새로운 Promise객체를 만들었다. 그리고 인자로 전달하는 함수가 executer 함수이다. executer함수는 새로운 Promise객체가 생성되었을때 자동으로 실행된다. 즉, 위의 executer 함수는 new Promise라인 다음에 바로 실행된다. 

 

executer함수 안을 보자. 먼저 함수의 파라미터로 resolve, reject 콜백함수가 있다. 이들은 executer함수안의 프로세스가 실행된 후에 그에따른 결과를 인자로 담아 호출된다. 

 

만약 에러 없이 executer함수가 어떠한 데이터를 가져왔다면 resolve의 인자로 그 데이터가 전달된다. 만약 에러가 생겨 데이터를 가져오지 못했다면 에러가 reject의 인자로 담겨 전달된다. 

 

이 예제에선 우선 에러가 생기지 않았다고 가정하자.

 

executer 함수 안에서 호출된 resolve의 인자에 성공적으로 가져온 데이터가 있다. 그럼 이 데이터를 어떻게 접근할까? 바로 우리는 Promise의 프로토타입 객체있는 빌트인 메소드 then을 사용할 수 있다. 즉, 이제부터 우리는 프로미스의 주목적인 어떠한 프로세스를 실행하는 "exectuer"과 그 결과를 접근하는 "consumption"의 연결방법을 알아볼것이다.

 

우선 프로미스의 빌트인 메소드이니 프로미스를 통해서 호출된다.

 

p.then(); 

 

그리고 then 안에는 어떠한 함수가 있어야한다.

 

p.then(function(){  }) 

 

그리고 이 함수는 위의 resolve의 인자로 전달된 데이터가 파라미터로 전달된다.

 

p.then(function(data){console.log(data)})

 

이제 이 데이터를 가지고 무엇을 하든 자유다. 우리는 console.log만 했지만 여러가지 일이 가능하다.

 

반대로 만약에 에러가 발생했다면 우리는 그 에러를 .catch를 이용해 활용가능하다.

 

let p = new Promise(function(resolve, reject){//executer 함수
   //서버에서 아이디를 가져오는 프로세스가 실행됨
      let error = true;
      if(!error){
      resolve({id:'dodoBird'});
      }else{
      reject(new Error("can't find id"));
      }
})

p.then(function(data){console.log(data.id)})
 .catch(function(err){console.log(err)});

 .catch를 하지 않으면 uncaught에러가 발생한다.

 

위의 코드를 arrow함수를 사용한 버전으로 바꿔보겠다

let p = new Promise((resolve, reject)=>{//executer 함수
   //서버에서 아이디를 가져오는 프로세스가 실행됨
      let error = true;
      if(!error){
         resolve({id:'dodoBird'});
      }else{
         reject(new Error("can't find id"));
      }
})

p.then(data=>console.log(data.id))
 .catch(err=>console.log(err));

이렇게 더욱 깔끔하게 할 수 있다. 이건 나도 몰랐던건데 함수안에서 어떠한 함수를 호출을 하든 함수호출을 리턴하든 결과는 같다. 즉 p.then(data=>{console.log(data.id)}) 처럼 {}을 추가로 필요하지 않음.

 

그리고 then과 catch말고도 finally라는것이 있는데. 이것은 에러가 일어났든 데이터를 알맞게 가져왔든 실행된다. 그리고 인자로 아무것도 받지않아 그냥 "데이터를 받기 시작했습니다!" 같은 문자열처럼 "general"한 무언가를 실행할때 사용. 

 

그리고 우리는 catch를 이용해 에러를 받았는데. then의 두번째인자를 활용해도 된다.

 

이렇게만 보면 프로미스는 어떠한 프로세스를 실행하고 그 결과가 then에 담겨 오는 어떻게 보면 콜백하고 동일한 역활을 하는거 같다. 하지만 프로미스 체인이라는 것을 통해 프로미스의 엄청난 장점을 알아보자

 

프로미스 체인

 

우리는 비동기적 AJAX다음에 어떠한 로직을 실행시키기 위해 콜백과 프로미스를 사용할 수 있다는 것을 배웠다. 하지만 콜백은 위에서 배웠듯이 많은 비동기적 로직을 순차적으로 실행시키면 콜백지옥이 발생한다. 이것을 프로미스를 통해 바꿀 수 있다. 

 

이러한 코드를 보자

function multiply(value){
   return new Promise((resolve,reject)=>{
      setTimeout(()=>resolve(value),200);
   });
}

multiply(2).then((value)=>value*2)
   .then((value)=>value*2)
   .then((value)=>value*2)
   .then((value)=>console.log(value));

우선 동기식으로 프로미스 체인을 만들어봤다.

 

코드를 하나하나씩 봐보자

1. function multiply가 선언되었다 선언되기만 할뿐 안에있는 코드는 실행되지 않았기에 Promise의 execute function은 아직 실행되지 않는다.

2. 밑에 multiply(2)로 함수가 호출되었다 multiply함수는 new Promise를 리턴한다. 그러므로 이제야 Promise의 execute 함수가 실행된다.

3.이제 execute함수의 처리가 200ms 후 끝나고, 프로세스의 결과/데이터가 response의 인자로 담긴다.

4. response를 기다렸다가 then메소드를 이용해 접근가능하다. then 메소드는 파라미터로 함수를 받는데. 이 함수에는 execute함수의 결과가 담겨져 있다.

5. 프로미스 체인의 핵심은 받은 value를 (어떤 프로세스 후에) 리턴한다는 점이다.

6. 여기서 첫번째 then은 value*2(4)를 리턴한다. then 같은 promise handler method가 어떠한 값을 리턴하면 그 값은 프로미스의 결과처럼 여겨진다. 즉, 여기서 4를 리턴한다는 것은 resolve(4)랑 비슷한 의미이다. 그래서 중요한 포인트는 다음에 나올 then(아니면 다른 핸들러들)이 그 리턴된 값(4)를 자신의 함수의 파라미터로 받는다는 점이다. 여기서는 어떠한 비동기적 처리(setTimeout)등을 거치지 않기때문에 시간소요 없이 바로바로 처리가 된다.

7. 그렇게 결과는 16이 나온다.

 

앞에 6번에 말했듯이 우리는 여기서 그냥 value를 리턴한다. 그래서 처음 then은 프로미스의 execute 함수가 실행된다음 200ms이후의 비동기적 사용을 보여줬지만 다음 then들은 앞서 바로바로 리턴된 값을 delay없이 바로 실행됬다. 

 

그래서 이번엔 then 메소드가 value를 리턴하는 것이 아닌 프로미스를 리턴하는 것에 대해 알아볼것이다.

위의 코드를 이렇게 고쳐보자.

function multiply(value){
   return new Promise((resolve,reject)=>{
      setTimeout(()=>resolve(value),200);
   });
}


multiply(2).then((value)=>multiply(value*2))
   .then((value)=>multiply(value*2))
   .then((value)=>multiply(value*2))
   .then((value)=>console.log(value));

위코드와 달라진 점이 보이는가? 그렇다 이번엔 then 메소드가 그냥 value를 리턴하는게 아니라 함수 호출을 리턴하고 있다. 하지만 호출되는 함수가 프로미스를 리턴하고 있기에 프로미스를 리턴하고 있다고 봐도 좋다. 

 

즉 이러한 일이 일어나고 있는 것이다.

function multiply(value){
   return new Promise((resolve,reject)=>{
      setTimeout(()=>resolve(value),200);
   });
}

multiply(2).then((value)=>new Promise((resolve,reject)=>{
      setTimeout(()=>resolve(value*2),200);}))
      
   .then((value)=>multiply(value*2))
   .then((value)=>multiply(value*2))
   .then((value)=>console.log(value));

위와 같이 우리는 프로미스를 리턴하고 있다는 점을 알 수 있다. 그래서 그 프로미스 안에서 어떠한 비동기적 처리가 실행된다. 그리고 그 결과가 resolve되면. 우리는 resolve된 값을 다음 then메소드안에서 결과를 확인하고 활용가능하다.

 

즉 프로미스를 계속 리턴하고 then으로 그 값을 받아드려 비동기적 처리들을 순차적으로 처리하는 프로미스 체인을 만들 수 있다.

 

한번 이 코드를 콜백과 비교해보고 장점을 확인하자.

function multiply(value,callback){
   setTimeout(()=>
   callback(value)
   ,200);
}

multiply(2,function(result){
   multiply(result*2,function(result){
       multiply(result*2,function(result){
          multiply(result*2,function(result){
             console.log(result);
          })
       })
   })
})
function multiply(value){
   return new Promise((resolve,reject)=>{
      setTimeout(()=>resolve(value),200);
   });
}

multiply(2).then((value)=>multiply(value*2))
   .then((value)=>multiply(value*2))
   .then((value)=>multiply(value*2))
   .then((value)=>console.log(value));

보다시피 같은일을 하지만 프로미스 체인을 이용해 훨씬 더 깔끔한 코딩을 할수있다.

 

더 알아야될점. 프로미스를 체인하지 않고 아래 처럼 하면 전 라인이 독립적으로 프로미스를 처리하기에 결과를 전달할 수 없다.

multiply(2).then((value)=>multiply(value*2))
multiply(2).then((value)=>multiply(value*2))
multiply(2).then((value)=>multiply(value*2))
multiply(2).then((value)=>multiply(value*2))
   

 

주의해야될점. 우리는 위에서 then메소드가 리턴값으로 프로미스가 아닌 그냥 value를 가질 수 있다고 했다. 하지만 이런상황을보자

function add(x){
   return new Promise((resolve,reject)=>
      setTimeout(()=>{
         let sum = x+x;
         resolve(sum);
      },1000)
   )
}
function divide(value){
   setTimeout(()=>{
      let result = value/2;
      return result;//이렇게 하면 안됨!
      },2000);
}

add(2).then(result=>add(result))
   .then(result2=>divide(result2))
   .then(result3=>console.log(result3));

 위의 코드는 then(아니면 다른 handler)가 자신의 리턴값을 프로미스가 아닌 value로 할수 있다는 점을 이용했다. 보다시피 divide함수는 프로미스가 아닌 그냥 값을 리턴한다. 하지만 비동기적 함수(setTimeout)안에서 함수를 리턴하면 비동기적 함수 안에있는 로직이 실행되기도 전에 밖의 함수가 리턴된다. 그렇기에 비동기적 로직을 이용할때는 언제나 프로미스를 이용하자.

 

위의 코드를 고쳐보자

function add(x){
   return new Promise((resolve,reject)=>
      setTimeout(()=>{
         let sum = x+x;
         resolve(sum);
      },1000)
   )
}
function divide(value){
   return new Promise((resolve,reject)=>
      setTimeout(()=>{
      let err=false;
      if(!err){
         let result = value/2;
         resolve(result);
         }
      },200)
   )
}

add(2).then(result=>add(result))
   .then(result2=>divide(result2))
   .then(result3=>console.log(result3));

이렇게 비동기적 처리에서는 언제나 프로미스를 이용해 다음 체인에 쓰일 값을 resolve해줘야 한다.

 

즉 이렇게 정리할 수 있다.

어떠한 handler(then/catch/finally)가 프로미스를 리턴한다면, 동기적/비동기적 처리가 포함되어 있다는 뜻이기에 보통 비동기적 처리를 기다리고 resolve되냐 reject 되냐에 따라 다음 then/catch가 실행된다.

 

만약 그냥 value를 리턴한다면, 동기적 처리를 의미하고 resolve가 되어 바로 다음 then이 실행된다

 

만약 에러를 throw한다면, 동기적 처리를 의미하고 reject가 되어 바로 다음 catch가 실행된다.

 

 

그럼 이제 에러를 어떻게 핸들할것인지 대해서 알아보자

 

ERROR HANDLING

우선 어떠한 프로미스의 executer이 성공적으로 resolve되는 상황을 다루어왔다.

하지만 이번엔 어떠한 랜덤 에러가 일어나서 reject되는 상황을 생각해보자.

 

function sample(){
   return new Promise((resolve,reject)=>{
       reject(new Error('ERROR! blah'));
   });
}

sample().catch(alert);

이렇게 에러가 일어나서 state가 reject가 되어 .catch가 실행되는 상황들을 다룰것이다. 

 

catch안에 alert밖에없다. 이유를 먼저 설명하겠다.

catch(err=>alert(err));

위의 코드는 reject된 에러를 err에 받아 err을 alert한다. 하지만 저 함수는

catch(function alert(err){ native code })랑 결과적으로 같은 일을 한다

그래서 그냥 alert함수를 참조하는 alert만 써도 된다.

catch(alert);

 

function add(x){
   return new Promise((resolve,reject)=>
      setTimeout(()=>{
         let sum = x+x;
         resolve(sum);
      },1000)
   )
}
function divide(value){
   return new Promise((resolve,reject)=>
      setTimeout(()=>{
      let err=true;
      if(!err){
         let result = value/2;
         resolve(result);
         }else{
         reject(new Error('Error Error~!'))
      } 
      },200)
   )
}

add(2).then(result=>add(result))
   .then(result2=>divide(result2))
   .then(result3=>console.log(result3))
   .catch(alert);

 

위의 코드를 하나씩 살펴보자

1.add(2)가 invoke되면서 new Promise가 리턴된다.

2. execute 함수가 처리되길 기다린다. Promise가 resolve되었으니 그 값이 then에게 전달된다.

3. then 핸들러는 받은 결과로 add(result)가 호출되고 new Promise가 리턴된다. 위와 같이 처리되길 기다렸다가 resolve되고 다음 then이 실행된다.

4. 여기서부터 중요하다. result2를 받은 then핸들러가 이번엔 divide함수를 호출하고 다른 new Promise가 리턴된다. 여기서는 에러가 일어나서 promise가 reject된다. 그러므로 다음then이 실행되지 않고 가장 가까운 catch로 가서 에러가 어떻게 처리될것인지 정한다. 

5.catch안에는 빌트인 alert함수가 있다. 그 함수는 에러값을 파라미터로받아 에러를 alert한다.

 

이렇게 어떠한 에러가 일어나면 모든 then 핸들러를 뛰어넘고 바로 catch로 간다. 그러므로 catch를 여러번 쓸 필요없이 체인의 마지막에 달아주면 된다.

 

Catch의 rethrowing 활용

 

.then(result=>doSomething(result))
.catch(err=>{
if(//만약 처리 가능한 에러라면)
//여기서 어떻게 에러를 처리함

else//처리불가능한 에러면
throw err;
)}
.then(result2=>doSomething(result2))//처리가능한 에러면 처리 다음 then을실행
.catch(err=> console.log(err))//불가능하면 다음 catch실행

 이렇게 catch를 활용하여 처리가능하면 코드를 지속하고 처리불가능하면 에러를 뜨게 만드는 코드를 만들 수 있다.

 

비동기 환경에서 에러가 일어나 state를 바꾸고 싶을땐 무조건 reject를 사용하자.

동기적인 프로미스에서는 에러가 일어났을때 throw new Error('에러다!')이렇게 throw문을 사용해도 catch가 받는다. 하지만 비동기적인 프로미스에서 에러가 일어났을때는 throw말고 무조건 reject(new Error('에러에요!'))이렇게 써야지 catch가 받을 수 있다.

 

이유는 먼저 executer가 코드를 execute 할때 throw를 실행하는데. 비동기적 환경에서는 executer가 throw를 지나치기 때문.

 

그래서 동기적 환경에서는 throw/reject가 둘다 state를 바꿀 수 있지만, 비동기적 환경에서는 reject 콜백만 가능하다. 그래서 프로미스를 쓸떄에는 가능한 reject를 사용하자. 

https://javascript.info/promise-error-handling<<아주 좋은 사이트

 

Promise.all등 Promise의 다른 메소드들도 위의 사이트에서 확인해보자

 

'IT > 자바스크립트' 카테고리의 다른 글

AJAX  (0) 2020.05.16
[jQuery]애니메이션 더하기  (0) 2020.05.13
[jQuery] 이벤트 바인딩  (0) 2020.05.12
[jQuery] CSS/class 바꾸는 방법  (0) 2020.05.12
[jQuery] 콘텐트 제거 그리고 attribute 바꾸기  (0) 2020.05.12