본문 바로가기

Javascript/Concept

Javascript::Promise에 대한 이해




전통적인 callback 패턴

function mySandwich(param1, param2, callback) {
    alert('Started eating my sandwich.\n\nIt has: ' + param1 + ', ' + param2);
    callback();
}
 
mySandwich('ham''cheese'function() {
    alert('Finished eating my sandwich.');
});
cs

위와 같은 형식들자바스크립트에서 비동기 프로그래밍을 위한 패턴으로 callback을 사용했습니다. 하지만 이러한 전통적인 callback 패턴에는 몇가지 단점이 있습니다.

  • 비동기 처리 중에 발생한 오류를 예외처리 하기가 까다롭다. (http://blog.coderifleman.com/2014/11/15/javascript-and-async-error/ :: 비동기 에러처리의 어려움)

  • 여러 개의 비동기 로직을 한꺼번에 처리하는 데에 한계가 있다.

  • 코드의 복잡성이 증가하고 가독성이 떨어진다.


fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})
cs

fs 모듈을 사용한 파일을 처리에 관련된 소스코드입니다. 잦은 callback 함수의 사용으로 인해 소스코드가 직관적이지 않다는 것을 한눈에 알 수 있습니다. 모든 상황에 해당하는 것은 아니지만 몇몇의 특정 비동기적인 상황에서 그다지 유용한 패턴은아닙니다.


Promise란 무엇인가

비동기 프로그래밍을 위한 또 다른 패턴으로 Promise가 등장했고 Promise는 전통적인 callback 패턴이 가진 단점을 일부 보완( Promise가 callback이 가진 단점들에 대한 무조건적인 해결 방안이 되지는 않는다고 한다. )하여 비동기 처리 시점을 명확하게 표현합니다. Promise를 정의하면 비동기 처리 로직을 추상화 한 객체와 그것을 조작하는 방식이라고 할 수 있습니다. 이 개념은 E언어에서 처음 고안 되었으며 병렬 및 병행 프로그래밍을 위한 일종의 디자인 입니다. 이 디자인을 javascript 환경에 가져온 것이 Promise 입니다. Promise q async 같은 javascript library 제공되기 시작했으며 ES6 부터는 언어 레벨에서 제공하는 기능입니다상대적으로 가독성이 떨어지는 callback 패턴을 대체하여 async 함수도 sync 함수인 같은 flat 모양으로 코드를 작성하는 것이 장점입니다. Promise의 몇 가지 특징은 다음과 같습니다.

    • Promise를 지원하는 함수는 비동기 처리 로직을 추상화한 promise 객체를 반환한다.

    • 객체를 변수에 대입하고 성공 시 동작할 함수와 실패 시 동작할 함수를 등록해 사용한다.

    • 함수를 작성하는 방법은 promise 객체의 인터페이스에 의존한다. (promise 객체에서 제공하는 메서드만 사용해야 하므로 전통적인 callback 패턴처럼 인자가 자유롭게 결정되는 것이 아닌 같은 방식으로 통일된다.)

    • Promise라고 부르는 하나의 인터페이스를 이용해 다양한 비동기 처리 문제를 해경할 수 있다.(비동기 처리를 쉽게 패턴화 할 수 있다.) => Promise를 사용하는 큰 이유중 하나


Promise 살펴보기

Promise의 API, 워크플로우, 상태에 관하여 소개하며 Promise에 대한 개념을 익혀보려고합니다.


1. API

ES6 Promise 사양에서 정의하는 API는 일반적으로 크게 Constructor, Instance Method, Static Method 의 세 가지 종류가 있습니다.


Constructor

Promise는 XHR(XMLHttpRequest)처럼 생성자 함수에 new 연산자를 선언하여 promise 인스턴스 객체(promise 객체)를 생성해 사용합니다. Promise 객체를 생성하는 과정은 다음과 같습니다.

    1. new Promise(function)으로 promise 객체를 생성한다.

    2. function에는 비동기 처리를 작성한다.

      - 처리 결과가 정상이라면 resolve(결과 값)를 호출한다.

      - 처리 결과가 비정상 이라면 reject(error 객체)를 호출한다.

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…
  // 비동기 처리 작성
 
  if (/* everything turned out fine */) {
   
  resolve("Stuff worked!");
  
} else {
    
reject(Error("It broke"));
  
}
});
cs

위에서 생성한 Promise는 then()을 호출하여 다음과 같은 방법으로 사용할 수 있습니다.

promise.then(function(result) {

  console.log(result); // "Stuff worked!"

}, function(err) {

  console.log(err); // Error: "It broke"

});
cs



Instance Method

new 연산자로 생성한 Promise의 인스턴스 객체에는 성공(resolve) 또는 실패(reject)했을 때 호출된 콜백 함수를 등록할 수 있는 promise.then()이라고 하는 인스턴스의 메서드가 있다.


promise.then(onFulfilled, onRejected)
cs

성공했을 때는 onFulfilled가 호출되며 실패했을 때는 onRejected가 호출된다. onFulfilled, onRejected는 선택적 (optional) 인자이므로 생략할 수 있다. 이처럼 promise.then()으로 성공 또는 실패 시의 동작을 동시에 등록할 수 있다. 만약 오류 처리만 하려면 promise.then(undefined, onRejected)와 같은 의미인 promise.catch(onRejected)를 사용하면 된다.


promise.catch(onRejected)
cs

Static Method

전역객체인 Promise에는 Promise.all()이나 Promise.resolve() 같은 정적 메서드가 있습니다. 이 메서드는 Promise를 다루는 데 필요한 보조 메서드입니다.



2. 워크플로우

function asyncFunction() {

    return new Promise(function (resolve, reject) {

        setTimeout(function () {

            resolve('Async Hello world');

        }, 16);
    });
}
 

asyncFunction().then(function (value) {

    console.log(value); // => 'Async Hello world'

}).catch(function (error) {

    console.log(error);

});
cs

    1. asyncFunction()은 Promise 생성자를 new 연산하여 promise 객체를 반환한다.
    2. 비동기 처리가 끝났을 때 호출될 callback을 promise 객체의 then()으로 등록한다. 
    3. 실패했을 때 호출될 callback은 catch()로 등록한다. setTimeout()으로 16ms 이후에 성공하도록 작성했으므로 16ms 이후 then()으로 등록한 callback이 호출되어 "Async Hello world"라는 메세지가 출력된다.
    4. 위의 경우 catch()로 등록한 callback은 호출되지 않는다. 하지만 setTimeout()이 존재하지 않는 환경이라면 예외가 발생하므로 catch()로 등록한 callback 함수가 호출된다.
    5.  catch()는 promise.then(onFulfilled, onRejected)로 대채하여 작성 할 수 있다.

asyncFunction().then(function (value) { 
console.log(value);

}, function (error) { 

console.log(error);

});
  cs



3. 상태

Promise 생성자 함수를 new 연산하여 생성한 Promise 객체에는 다음과 같은 세 가지 상태(State)가 존재합니다.  Promise 상태의 종류와 변화는 매우 단순합니다.


 ES6 Promise 사양에서의 명청

 Promise / A+ 에서의 명칭

 설

 "has-resolution"

 Fulfilled

  • 성공(resolve)했을 때의 상태
  • onFulfilled가 호출된다.

 "has-rejection"

 Rejected

  • 실패(reject)했을 때의 상태
  • onRejected가 호출된다.

 "unresolved"

 Pending

  • 성공도 실패도 아닌 상태
  • Promise 객체가 생성된 초기 상태를 말한다.

Promise 객체의 상태를 직접 조작할 일은 없으므로 이름 자체에 크게 신경쓰지 않아도 됩니다. 다음은 Promise/A+의 명칭인 Pending, Fulfilled, Rejected를 이용한 상태에 관한 설명입니다.


[[PromiseState]]

ECMAScript - DRAFT문서(https://tc39.github.io/ecma262/#sec-promise-objects)에 따르면 Promise의 상태는 [[PromiseState]]라고 하는 내부 정의에 의해 결정됩니다. 하지만 [[PromiseState]]에 접근할 수 있는 API가 없어서 기본적으로 알 수 없습니다.

Promise 객체는 Pending 상태로 시작해 Fulfilled나 Rejected상태가 되면 다시는 변화하지 않는다. 그래서 Fulfilled 및 Rejected 상태를 Settled(불변) 상태라고 합니다. 즉, Event 리스너와는 다르게 then()으로 등록한 callback 함수는 한 번만 호출됩니다. 정리하자면  then()은 promise 객체의 상태가 변화할 때 한번만 호출될 callback함수를 등록하는 메서드인 것입니다.


Settled 상태

성공 또는 실패 했을 때의 상태를 말합니다. Pending과 Settled는 서로 대응하는 관계라고 할 수 있습니다.




마치며

Promise에 대한 궁금증을 가지고 정리를 시작하였는데 생각보다 분량이 많고 요구하는 지식적인 깊이가 깊었습니다. 아직 Javascript의 코어적인 부분이 많이 부족해서인지 Promise의 정의나 간단한 문법과 사용법까지 정리를 해보았는데, Promise가 가진 구체적인 사용과 특징과 관련된 Promise에 대한 좀더 깊은 탐구는 추후에 다시 정리할 예정입니다.



살펴봐야할 문서

https://speakerdeck.com/kerrick/javascript-promises-thinking-sync-in-an-async-world :: JavaScript Promises–Thinking Sync in an Async Word ( Promise 상태변화 )




정리 하면서 참조한 문서

https://developers.google.com/web/fundamentals/getting-started/primers/promises :: 자바스크립트 프라미스:소개

http://callbackhell.com/ :: callback hell

http://www.sohamkamani.com/blog/2016/08/28/incremenal-tutorial-to-promises/ :: An incremental tutorial on promises in javascript

Hanbit eBook :: Javascript_promise

http://han41858.tistory.com/11 :: Promise를 사용하는 두 가지 방법, new Promise, Promise.resolve()