-
Notifications
You must be signed in to change notification settings - Fork 83
[ES6] Promises(1): the API
이 문서는 http://www.2ality.com/2014/10/es6-promises-api.html 를 번역한 내용입니다.
이번 포스트는 일반적인 promise를 통한 비동기 프로그래밍과 ES6 promise API에 일부를 소개하고자 합니다.
2개의 비동기 프로그래밍 포스트 중 2번째이며, 충분히 이해하기 위해선 1번째 포스트를 읽어보는 것이 좋을 것입니다.
Promises는 비동기 프로그래밍의 한 부분을 도와주는 패턴입니다.(함수 또는 메서드를 비동기적으로 결과를 받는 것) 이런 기능을 구현하기 위해선 promise 를 반환해야 합니다. 이러한 함수를 구현하기 위해서는 결과에 대해 위임하는 객체인 promise를 반환해야 합니다.
함수 호출자는 결과가 연산 되었으면 통지 받을 promise와 함께 콜백을 등록합니다.
자바스크립트 promises의 사실상 표준은 Promises/A+ 입니다. ECMAScript6 promise API는 이 표준을 따릅니다.
아래의 첫 번째 예제를 보면 어떻게 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는 결과(then()
,catch()
)에서 호출되는 메서드를 통해 제공됩니다. (B)라인 안에 error 콜백은 2가지의 편리성을 가지고 있습니다.
첫 번째, 에러 처리의 단일 스타일입니다.
두 번째로, 당신은 readFilePromisified()
와 (A)라인에서 콜백 오류를 핸들링 할 수 있습니다.
promise가 생산자(Producer)와 소비자(consumer) 측면에서 어떻게 작동하는지 알아봅시다.
생산자는 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는 한 번 처리되면 그 처리 상태로 유지됩니다.
new Promise()
의 파라미터를( (A)라인 시작점 ) 실행자(executor 모호한 단어라 이하 영문표기로 명칭)라고 부릅니다.
- 만약 연산이 잘 되었다면, executor는
resolve()
통해 결과를 전송합니다. 보통 promise 완료(fulfills)를 말합니다.(promise가 resolve였지만 실제로 아닐 경우 뒤에 설명하겠습니다.) - 만약 에러가 발생할 경우, executor는
reject()
를 통해 promise-소비자(consumer)에게 통보 합니다. 즉 promise는 거절(reject) 상태입니다.
promise 소비자(consumer)로서, 당신은 반응(reactions)을 통해 완료(fulfillment)
혹은 거절(rejection
) 상태에 대해 'then()` 메소드에 등록한 콜백 함수로부터 통보를 받게 됩니다.
promise.then(
function (value) { /* fulfillment */ },
function (reason) { /* rejection */ }
);
promise가 비동기 함수(일회성 결과)에 유용한 점은, promise상태가 한 번 확정되면 더이상 변하지 않게 된다는 점이다.
게다가 promise가 확정(settled)되기 전이나 후에 당신이 then()
을 호출 했는지는 중요하지 않기 때문에 어떤 경쟁상태(race condition)도 존재하지 않습니다.
- 전자의 경우, promise 상태가 확정(settled)되는대로 바로 적절한 반응(reaction)이 호출됩니다.
- 후자의 경우, promise 결과(fulfillment 또는 rejection 값)가 캐시 되어, 적절하게 원하는 타이밍에 즉시
then()
다룰수 있게 해줍니다.(task로 큐에 저장)
만약 당신이 성공에만 관심 있다면, then()
의 2번째 파라미터를 생략할 수 있습니다.
promise.then(
function (value) { /* fulfillment */ }
);
만약 당신이 거절(reject)에만 관심 있다면, 1번째 파라미터를 생략할 수 있습니다. catch()
메서드는 같은 작동을 하게 해주는 더 간단한 방법입니다.
promise.then(
null,
function (reason) { /* rejection */ }
);
// Equivalent:
promise.catch(
function (reason) { /* rejection */ }
);
성공(fulfillments)을 위해서는 then()
을 사용하고 에러는 catch()
를 사용하는 것을 추천합니다. 왜냐하면 catch
는 콜백에 멋진 라벨을 지정하고 또 동시에 여러 promise의 거절(rejections)을 처리할 수 있습니다. (어떻게 하는지 나중에 설명)
몇 가지 예제를 통하여 이러한 기본 구성요소를 사용해봅시다.
이벤트 기반인 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);
});
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)라인에 성공(fulfillment) 결과 값은 필요 없습니다. 그냥 통보 받는것 만으로도 충분합니다.
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)라인) 요청을 취소하진 않지만, 그 결과로 promise가 수행되지 않도록 방지할 뿐입니다.
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);
});
메소드 호출 결과는 새로운 promise Q입니다.
P.then(onFulfilled, onRejected)
즉 Q의 then()
을 호출하여 promise기반 흐름을 제어할 수 있게 유지 한다는 것입니다.
-
Q는
onFulfilled
또는onRejected
중 하나에 의해 반환된 것으로 해결(resolved
) 합니다. -
Q는
onFulfilled
또는 예외를 던진onRejected
중 하나에 의해 거절(rejected
) 합니다.
만약 then()
에 의해 반환된 promise Q의 값을 정상적인 값으로 해결(resolve)하면, 그 다음 then()
을 통해 해당 값을 받을 수 있습니다.
asyncFunc()
.then(function (value1) {
return 123;
})
.then(function (value2) {
console.log(value2); // 123
});
또한 당신은 then()
에 의해 반환되 promise Q를 thenable R로 해결(resolve) 할 수 있습니다. A thenable 은 promise 스타일 : then()
메서드를 가진 객체입니다. 그래서 promises는 thenable 입니다.
R을 이용하여 해결(resolve)하는 것은 Q 후에 삽입 된다는 것을 의미합니다.(예를들어 onFulfilled
에서 반환) : R의 상태는 Q의 onFulfilled
, onRejected
콜백에 전달 됩니다. 어떤 면에서는 Q는 R이 됩니다.
이 메카니즘의 주요한 점은 아래 예시와 같은 중첩된 then()
호출을 평평하게(flatten) 해주는 것 입니다.
asyncFunc1()
.then(function (value1) {
asyncFunc2()
.then(function (value2) {
...
});
})
평평한(flat) 버전은 다음과 같습니다.
asyncFunc1()
.then(function (value1) {
return asyncFunc2();
})
.then(function (value2) {
...
})
이어서 2번째 챕터에서 계속 이어집니다.
- 번역 문서를 읽는 중, 오타나 어색한 문장이 있으면 이슈를 등록해주세요!