Notice
Recent Posts
Recent Comments
Link
«   2024/03   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개발블로그

JS Promise (2018.10.10) 본문

JS Promise (2018.10.10)

학교옆메추리 2020. 5. 28. 13:57
A Promise is an object representing the eventual completion or failure of an asynchronous operation. — (출처: MDN)

‘Promise는 비동기 작업의 최종 완료나 실패를 표현하는 객체이다.’ 쯤으로 해석된다.

Promise에 대해서 보기 전에 ‘비동기 작업’에 대하여 알아보도록 하자.

Asynchronous

자바스크립트는 single-thread로 동작한다. 쉽게 말하자면 하나씩 하나씩 순차적으로 처리해 나간다는 말이다.

그림을 보자.

사용자의 동작에 의해서 이벤트가 발생하고, 그 이벤트는 메시지 큐에 담긴다. 큐의 맨 앞 이벤트가 실행될 때, 호출스택에 이벤트에 해당하는 함수들이 쌓이고, 순차적으로 처리된다. 전부 다 처리된 후에, 다음 이벤트를 받아와 똑같이 반복한다.

여기서 실행되는 함수 중간에 수행완료까지 많은 시간이 소요되는 작업이 있다면 어떻게 될까? 그 작업이 수행될때까지 기다렸다가 완료되면 다음 작업을 실행할 것이다. 그러면 그 동안에는 아무런 작업도 할 수 없게 된다.

예를 들어 보자, 버튼을 클릭하면 서버에 요청을 보내고 받은 응답을 화면에 출력해줄것이다. 응답이 오는데는 10초정도 시간이 소요된다. 순차적(동기적 Synchronous)으로 동작한다면 사용자는 버튼을 누른 후 10초동안 아무 일도 하지 못한다. 작업수행이 완료되지 않았으므로, 메시지 큐에 이벤트가 쌓이긴 하나 호출스택 올라가지 않기 때문이다.

그래서 Asynchronous 즉, 비동기적으로 이런 작업들을 실행한다.

비동기작업이 수행될 때, 자바스크립트 엔진이 처리하지 않고 Web API에게 작업을 요청함과 동시에 콜백함수를 전달한다. 그리고 스택에서 해당 작업을 pop한 후 다음 작업을 실시한다. 그 동안 Web API는 작업을 수행하고, 완료 시에 전달받은 콜백함수를 메시지 큐에 넣는다. 이후에 순차적으로(큐에 담긴 순서대로) 작업을 수행하면서 콜백함수도 역시 실행되는 것이다.

자, 이제 다시 Promise로 돌아가자

Promise가 나오기 전에…

앞서 말했듯이 비동기처리가 필요하면, 수행 완료 이후에 실행될 콜백 함수를 같이 넘긴다.

function sayLoveYou() {
 setTimeout(function(){
  var subject = 'I';
  console.log(subject);
   
  setTimeout(function() {
    var verb = 'love';
    console.log(verb);
    
    setTimeout(function() {
      var object = 'you';
      console.log(object);
    }, 1000);
    
  }, 1000);
  
 }, 1000) ;
  
}

sayLoveYou(); // I love you

여기서의 문제는, 비동기처리가 지금은 3번 연속이지만, 5번 10번 반복될 때 일어난다. 콜백에 콜백에 콜백에 콜백에 콜백…… 우리는 이것을 Callback Hell이라고 부른다.

Promise

ES6에서 Promise가 등장했다!
위의 문제점인 callback hell에서 벗어날 수 있는 좋은 방법이다.
promise는 구현에 의해 즉시실행되고, 인자로 resolve, reject 두 함수를 전달한다. 우리는 이 두 함수를 통해 promise를 해결(resolve)하거나 또는 거부(reject, 예외상황 발생 시!)할 수 있다.

const saySubject = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const subject = 'I';
      resolve(subject);
    }, 1000);
  })
}

const sayVerb = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const verb = 'love';
      resolve(verb);
    }, 1000);
  })
}

const sayObject = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const object = 'you';
      resolve(object);
    }, 1000);
  })
}

const sayLoveYou = () => {
  saySubject()
  .then(subject => {
    console.log(subject);
    return sayVerb();
  })
  .then(verb => {
    console.log(verb);
    return sayObject();
  })
  .then(object => {
    console.log(object);
  })
}

sayLoveYou();  // I love you

 

코드에서 볼 수 있듯이 then으로 resolve의 인자들을,
코드에는 없지만 catch로 reject의 인자들을 받을 수 있다.

then 내부에서 Promise객체를 리턴하면 그 다음 then에서 받을 수 있기에 코드가 훨씬 간결하고 편해진다. 이것을 Promise Chain이라고 한다.

ES9 (ECMA2018) 에서는 Promise에서 .finally()를 사용할 수 있다고 한다.

Async / Await

ES8에서는 async / await 이 등장했다!
이를 사용하면 비동기 코드의 겉모습, 동작을 조금 더 동기 코드와 유사하게 만들어줄 수 있고, 무엇보다 Promise에 비해 코드가 간결해지는 장점이 있다.

const saySubject = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const subject = 'I';
      resolve(subject);
    }, 1000);
  })
}

const sayVerb = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const verb = 'love';
      resolve(verb);
    }, 1000);
  })
}

const sayObject = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const object = 'you';
      resolve(object);
    }, 1000);
  })
}

const sayLoveYou = async () => {
  const subject = await saySubject();
  console.log(subject);
  
  const verb = await sayVerb();
  console.log(verb);
  
  const object = await sayObject();
  console.log(object);
}

sayLoveYou(); // I love you

await키워드는 오직 async키워드가 붙은 function 내부에서만 사용할 수 있다.

여기서 궁금한 점이 생겼다. 에러처리는 어떻게하지?

답은 매우 쉽다. try-catch구문을 이용하면 된다!
하지만 여기서 또다른 궁금증이 생겼다….
비동기로 처리할 함수가 많은데, 각각마다 수행할 에러핸들링이 다르다면?!

// in async function...
try {
  const foo = await findFoo();
} catch(e) {
  console.log('there is no foo!')
}

try {
  const bar = await findBar();
} catch(e) {
  console.log('there is no bar!')
}

try {
  const baz = await findBaz();
} catch(e) {
  console.log('there is no baz!')
}
// ...more and more

위에서 보이듯이 매번 await을 사용할 때마다(promise를 소비할 때마다) 
try-catch구문으로 감싸주어야 하므로 큰 불편함이 느껴진다….

// for easy Error Handling
const to = promise => {
  return promise
    .then(data => [null, data])
    .catch(err => [err])
}
           

// using to function
const easyErrorHandlingAsync = async () => {
  let err, foo, bar, baz;
    
  [err, foo] = await to(findFoo());
  if(err) {
    // error handling here
    console.log('there is no foo!')
  }

  [err, bar] = await to(findBar());
  if(err) {
    // error handling here
    console.log('there is no bar!')
  }
    
  [err, baz] = await to(findBaz());
  if(err) {
    // error handling here
    console.log('there is no baz!')
  }
}

 

to 함수에서는 promise객체를 받아서 then과 catch에서 배열객체를 반환한다. 배열의 첫 번째 요소는 에러, 두 번째 요소는 비동기 작업의 결과값이다.
정상적 작업완료 시에는 err값이(배열 첫 번째 요소값이) null이고, 비 정상적 작업종료 시에는 null이 아니기 때문에 우리는 if문을 통하여 보다 편하게 에러를 핸들링할 수 있게 된다.

'' 카테고리의 다른 글

HTTP/1.1 vs HTTP/2  (0) 2020.05.29
DEVIEW Review(2018.10.13)  (0) 2020.05.28
JS this (2018.10.06)  (0) 2020.05.28
JS Class (2018.10.04)  (0) 2020.05.28
JS prototype (2018.10.01)  (0) 2020.05.28