Skip to content

[ES6] Promises(1): the API

KwanUngPark edited this page Oct 17, 2016 · 22 revisions

이 문서는 http://www.2ality.com/2014/10/es6-promises-api.html 를 번역한 내용입니다.

#목차

  1. Promises
  2. 첫번째 예제
  3. Promises 생성과 사용
  4. promise 생산(Producing a promise)
  5. promise의 사용(Consuming a promise)
  6. 성공 또는 거절만 처리(Only handling fulfillments or rejections)
  7. 예제(Examples)
  8. 예제:promisifying XMLHttpRequest
  9. 예제: delaying an activity
  10. 예제: timing out a promise(promise의 시간 초과)
  11. then() 체이닝
  12. 일반 값으로 해결(Resolving with normal values)
  13. Resolving with thenable(thenable로 해결)

이번 포스트는 일반적인 promise를 통한 비동기 프로그래밍과 ES6 promise API에 일부를 소개하고자 합니다. 2개의 비동기 프로그래밍 포스트 중 2번째이며, 충분히 이해하기 위해선 1번째 포스트를 읽어보는 것이 좋을 것입니다.

#1. Promises Promises는 비동기 프로그래밍의 한 부분을 도와주는 패턴입니다.(함수 또는 메서드를 비동기적으로 결과를 받는 것) 이런 기능을 구현하기 위해선 promise 를 반환해야 합니다. promise는 결과를 위해 지정해놓은 객체를 의미합니다.
함수 호출자는 결과 계산이 완료되었다는 것을 알림 받기 위해 콜백을 promise로 등록합니다.

자바스크립트 promises의 사실상 표준은 Promises/A+ 이라고 부릅니다. ECMAScript6 promise API는 표준을 따릅니다.

#2. 첫번째 예제 아래의 첫 번째 예제는 promise로 어떻게 동작하는지 보여주기 위함이다.
Node-js스타일 콜백으로 비동기적으로 파일 읽는 법은 다음과 같습니다.

    fs.readFile('config.json',
        function (error, text) {
            if (error) {
                console.error('Error while reading config file');
            } else {
                try {
                    var obj = JSON.parse(text);
                    console.log(JSON.stringify(obj, null, 4));
                } catch (e) {
                    console.error('Invalid JSON in file');
                }
            }
        });

같은 기능을 promise 로 구현하면 다음과 같습니다.

    readFilePromisified('config.json')
    .then(function (text) { // (A)
        var obj = JSON.parse(text);
        console.log(JSON.stringify(obj, null, 4));
    })
    .catch(function (reason) { // (B)
        // File read error or JSON SyntaxError
        console.error('An error occurred', reason);
    });

promise도 여전히 콜백이 존재합니다. 그러나 promise는 결과를 호출 할 수 있는 메서드를 통해 제공됩니다. (then(),catch()) (B)라인 안에 error 콜백은 2가지의 편리성을 가지고 있습니다. 첫 번째, 에러 처리를 한가지 방식으로 할 수 있습니다. 두 번째로, 당신은 readFilePromisified()와 (A)라인의 콜백 둘다 에러를 핸들링 할 수 있습니다.

#3. Promises 생성과 사용

promise가 어떻게 작동하는지 공급자(Producer)와 소비자(consumer) 측면에서 알아봅시다.

3-1. promise 생산(Producing a promise)

공급자로서, promise를 생성하고 결과를 전송합니다.

    var promise = new Promise(
        function (resolve, reject) { // (A)
            ...
            if (...) {
                resolve(value); // success
            } else {
                reject(reason); // failure
            }
        });

promise는 항상 아래 3가지 상태 중(상호배타적인) 1가지 상태입니다.

  • 대기(pending) : 아직 결과 처리가 안 됐다.
  • 성공(Fulfilled) : 성공적으로 완료되었다.
  • 거절(rejected) : 처리되는 동안 실패가 발생하였다.

promise는 연산이 끝난 뒤 fulfilled 혹은 rejected 상태로 처리(settle)됩니다. promise는 한 번 처리되면 그 처리 상태로 유지됩니다.

promise state

new Promise()의 파라미터는( (A)라인 시작점 ) 집행자(executor 모호한 단어라 이하 영문표기로 명칭)라고 부릅니다.

  • 만약 연산이 잘 되었다면, executorresolve() 통해 결과를 전송합니다. 보통 promise 성공(fulfills)을 말합니다.(promise가 resolve였지만 실제로 아닐 경우 뒤에 설명하겠습니다.)
  • 만약 에러가 발생할 경우, executorreject()를 통해 promise-소비자(consumer)에게 통보 합니다. 즉 promise는 거절(reject) 상태입니다.

##3.2 promise의 사용(Consuming a promise) promise 소비자(consumer)로서, 당신은 fulfillment 혹은 rejection 이라는 반환 상태에 따라 then() 메소드에 등록한 콜백 함수로부터 알림을 받게 된다.

    promise.then(
        function (value) { /* fulfillment */ },
        function (reason) { /* rejection */ }
    );

비동기 함수를 위한 promise가 어떤 점에서 좋냐면, promise상태가 한 번 설정되면 더이상 어느 것도 변하지 않게 된다는 점이다. 게다가 비동기 함수들은 경쟁상태(race condition)가 절대 되지 않는다. 왜냐하면 then()을 실행하는 게 promise가 처리되기 전이냐 후냐는 중요하지 않기 때문이다. (promise는 어차피 resolvereject에 의해 진행되므로)

  • 전자의 경우, promise 상태가 세팅된 직후 호출됩니다.
  • 후자의 경우, promise 결과(fulfillment 또는 rejection 값)는 캐시 되고, 적절하게 원하는 타이밍에 즉시 then() 다룰수 있게 해준다.(task로 큐에 저장이 되어지고)

##3.3 성공 또는 거절만 처리(Only handling fulfillments or rejections) 만약 당신이 성공에만 관심 있다면, then()의 2번째 파라미터를 생략할 수 있습니다.

    promise.then(
        function (value) { /* fulfillment */ }
    );

만약 당신이 거절(reject)에만 관심 있다면, 1번째 파라미터를 생략할 수 있습니다. catch()메서드는 같은 작동을 하게 해주는 더 적합한 방법입니다.

    promise.then(
        null,
        function (reason) { /* rejection */ }
    );
    
    // Equivalent:
    promise.catch(
        function (reason) { /* rejection */ }
    );

catch()메서드를 사용하는 것은 성공 상태를 배제하고 오류만 잡기 위하여 then()을 사용할 때 추천하는 방식입니다. 왜냐하면 catch는 나이스한 콜백 식별자이며, 또 동시간대에 여러개 promise의 거절상태를 처리할 수 있습니다. (어떻게 하는지 나중에 설명)

#4. 예제(Examples)

몇 가지 예제를 통하여 기본적인 빌딩 블록(코드 블록's)을 사용해봅시다.

##4.1 예제:promisifying XMLHttpRequest 이벤트 기반인 XMLHttpRequest API 통해 HTTP GET 메서드를 수행하는 promise기반 함수입니다.

    function httpGet(url) {
        return new Promise(
            function (resolve, reject) {
                var request = new XMLHttpRequest();
                request.onreadystatechange = function () {
                    if (this.status === 200) {
                        // Success
                        resolve(this.response);
                    } else {
                        // Something went wrong (404 etc.)
                        reject(new Error(this.statusText));
                    }
                }
                request.onerror = function () {
                    reject(new Error(
                        'XMLHttpRequest Error: '+this.statusText));
                };
                request.open('GET', url);
                request.send();    
            });
    }

httpGet() 사용방법

    httpGet('http://example.com/file.txt')
    .then(
        function (value) {
            console.log('Contents: ' + value);
        },
        function (reason) {
            console.error('Something went wrong', reason);
        });

##4.2 예제: delaying an activity promise 기반으로 setTimeout()을 구현한 delay()함수(Q.delay()랑 비슷)

    function delay(ms) {
        return new Promise(function (resolve, reject) {
            setTimeout(resolve, ms); // (A)
        });
    }
    
    // Using delay():
    delay(5000).then(function () { // (B)
        console.log('5 seconds have passed!')
    });

(A)라인에서 파라미터 없이 resolve를 호출합니다.(resolve(undefined)를 호출하는 것과 동일). (B)라인에 성공 결과 값은 필요 없습니다. 그냥 통보만 할 뿐 충분합니다.

##4.3 예제: timing out a promise(promise의 시간 초과)

    function timeout(ms, promise) {
        return new Promise(function (resolve, reject) {
            promise.then(resolve);
            setTimeout(function () {
                reject(new Error('Timeout after '+ms+' ms')); // (A)
            }, ms);
        });
    }

시간 경과 이후 거절((A)라인) 요청을 취소하진 않으면, 성공 결과가 수행(return)되지 않도록 방지할 뿐입니다.

timeout()메서드를 사용하면 다음과 같습니다.

    timeout(5000, httpGet('http://example.com/file.txt'))
    .then(function (value) {
        console.log('Contents: ' + value);
    })
    .catch(function (reason) {
        console.error('Error or timeout', reason);
    });

#5. then() 체이닝 메소드 호출 결과는 새로운 promise Q입니다.

    P.then(onFulfilled, onRejected)

이 의미는 Qthen()을 호출을 통해서 promise기반 흐름을 제어할 수 있게 유지 한다는 것입니다.

  • QonFulfilled 또는 onRejected 중 하나에 의해 반환된 것으로 resolved 합니다.
  • QonFulfilled 또는 예외를 던진 onRejected중 하나에 의해 rejected 합니다.

##5.1 일반 값으로 해결(Resolving with normal values) 만약 당신이 일반값으로 then()에 의해 반환되는 promise Q를 해결(resolve)하면, 그다음 then()을 통해 일반 값을 받을 수 있습니다.

    asyncFunc()
    .then(function (value1) {
        return 123;
    })
    .then(function (value2) {
        console.log(value2); // 123
    });

##5.2 Resolving with thenable(thenable로 해결) 또한 당신은 thenable R을 반환하는 then()으로 promise Q를 해결(resolve) 할 수 있습니다. A thenablepromise 스타일 : then()메서드를 가진 객체입니다. 그래서 promisesthenable 입니다. R로 해결(resolve)하는 의미는 Q 이후에 삽입된다는 것을 말합니다.(예를들어 onFulfilled로 반환 하는 것) : R의 상태는 QonFulfilled , onRejected 콜백에 전달 됩니다. 어떤 면에서는 Q가 R이 되는 겁니다.

thenable R

이 메카니즘의 주요한 점은 아래 예시와 같은 중첩된 then() 호출을 평평하게(flatten) 해주는 것 입니다.

    asyncFunc1()
    .then(function (value1) {
        asyncFunc2()
        .then(function (value2) {
            ...
        });
    })

평평한(flat) 버전은 다음과 같습니다.

    asyncFunc1()
    .then(function (value1) {
        return asyncFunc2();
    })
    .then(function (value2) {
        ...
    })

이어서 2번째 챕터에서 계속 이어집니다.

위키

기술 문서
Clone this wiki locally