제작 코드 (producing code) : 원격에서 스크립트 불러오기 같은 시간이 걸리는 일
소비 코드 (consuming code) : 제작 코드의 결과를 기다렸다가 이를 소비한다. 소비의 주체(함수)는 여럿이 될 수 있다.
promise : 제작코드와 소비코드를 연결해 주는 자바스크립트 객체. 시간이 얼마가 걸리든 상관없이 약속한 결과를 만들어 내는 제작 코드가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 한다.
// Promise 객체
let promise = new Promise(function(resolve, reject) {
// executor
});
new Promise
에 전달되는 함수는 executor(실행자, 실행함수)라고 부른다
executor는 new Promise
가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다
executor의 인수 resolve
와 reject
는 자바스크립트에서 자체 제공하는 콜백이다
개발자는 resolve
와 reject
를 신경쓰지 않고 executor 내 코드만 작성하면 된다
⚠️ 대신 executor에선 결과를 즉시 얻든 늦게 얻든 상관없이 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 한다
resolve(value)
: 일이 성공적으로 끝난 경우 그 결과를 나타내는 value
와 함께 호출
reject(error)
: 에러 발생 시 에러 객체를 나타내는 error
와 함께 호출
👉 executor는 자동으로 실행되는데 여기서 원하는 일이 처리된다. 처리가 끝나면 executor는 처리 성공 여부에 따라 resolve
나 reject
를 호출한다
한편, new Promise
생성자가 반환하는 promise
객체는 다음과 같은 내부 프로퍼티를 갖는다
state
: 처음엔 "pending"
이었다가 resolve
가 호출되면 "fulfilled"
로 변하고 reject
가 호출되면 "rejected"
로 변한다
result
: 처음엔 undefined
이었다가 resolve(value)
가 호출되면 value
로, reject(error)
가 호출되면 error
로 변한다
let promise = new Promise(function(resolve, reject) {
// 프라미스가 만들어지면 executor 함수는 자동으로 실행
// 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되고
// result는 'done'이 된다
setTimeout(() => resolve("done"), 1000);
});
setTimeout
을 통해 executor 함수는 약간의 시간이 걸리도록 구현했다
executor '처리'가 시작되고 1초 후에 resolve("done")
이 호출되고 결과가 만들어진다
이 때 promise
객체의 state
는 "pending"
에서 "fulfilled"
가 되고, result
는 undefined
에서 "done"
이 된다
이처럼 일이 성공적으로 처리되었을 때의 프라미스를 fulfilled promise라고 부른다
let promise = new Promise(function(resolve, reject) {
// 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냄
setTimeout(() => reject(new Error("에러 발생")), 1000);
});
executor가 에러와 함께 약속한 작업을 거부하는 경우에는 1초 후 reject(...)
가 호출되면 promise
의 상태가 "rejected"
로 변한다
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 무시됨
setTimeout(() => resolve("…")); // 무시됨
});
executor는 resolve
나 reject
중 하나를 반드시 호출해야 한다
이 때 변경된 상태는 더 이상 변하지 않는다
처리가 끝난 프라미스에 resolve
와 reject
를 호출하면 무시되므로 executor에 의해 처리가 끝난 일은 결과 혹은 에러만 가질 수 있다
resolve
나 reject
는 인수 하나만을 받거나 아예 받지 않고 그 이외의 인수는 무시한다는 특성이 있다무언가 잘못된 경우, executor는 reject
를 호출해야 한다
이 때 인수는 resolve
와 마찬가지로 어떤 타입도 가능하지만 Error
객체 또는 Error
를 상속받은 객체를 사용하는 편이 좋다
executor는 대개 무언가를 비동기적으로 수행하고, 약간의 시간이 지난 후에 resolve
, reject
를 호출하는데 꼭 이렇게 할 필요는 없다
let promise = new Promise(function(resolve, reject) {
// 일을 끝마치는 데 시간이 들지 않음
resolve(123); // 결과(123)를 즉시 resolve에 전달함
});
어떤 일을 시작했는데 알고보니 이미 일이 끝나 저장까지 되어있는 경우 이렇게 resolve
나 reject
를 즉시 호출하는 방식을 사용할 수 있다
이렇게 하면 프라미스는 즉시 이행 상태가 된다
프라미스 객체의 state
, result
프로퍼티는 내부 프로퍼티이므로 개발자가 직접 접근할 수 없다
.then/.catch/.finally
메서드를 사용하면 접근 가능하다
프라미스 객체는 executor의 결과나 에러를 받을 소비 함수를 이어주는 역할을 한다
소비함수는 .then
, .catch
, .finally
메서드를 사용해 등록된다
프라미스에서 가장 중요하고 기본이 되는 메서드
promise.then(
function(result) { /* 결과(result)를 다룸 */ },
function(error) { /* 에러(error)를 다룸 */ }
);
.then
의 첫 번째 인수는 프라미스가 이행되었을 때 실행되는 함수이고, 여기서 실행결과를 받는다
.then
의 두 번째 인수는 프라미스가 거부되었을 때 실행되는 함수이고 에러를 처리한다
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 함수는 .then의 첫 번째 함수(인수)를 실행
promise.then(
result => alert(result), // 1초 후 "done!"을 출력
error => alert(error) // 실행되지 않음
);
만약 작업이 성공적으로 처리된 경우만 다루고 싶다면 아래 예시처럼 .then
에 인수를 하나만 전달하면 된다
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1초 뒤 "done!" 출력
.catch(f)
는 문법이 간결하다는 점만 빼고 .then(null, f)
와 같다
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)
과 같이 null
을 첫 번째 인수로 전달하면 된다
.catch(errorHandlingFunction)
을 써도 되는데 이는 .catch
는 .then
에 null
을 전달하는 것과 동일하게 작동하기 때문이다
프라미스는 처리(이행 혹은 거부)되면 f
가 항상 실행된다는 점에서 .finally(f)
호출은 .then(f, f)
와 유사하다
쓸모가 없어진 로딩 인디케이터를 멈추듯 결과가 어떻든 마무리가 필요할 때 finally
는 유용하다
new Promise((resolve, reject) => {
/* 시간이 걸리는 어떤 일을 수행하고
그 후 resolve, reject를 호출함 */
})
// 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
.finally(() => 로딩 인디케이터 중지)
.then(result => result와 err 보여줌 => error 보여줌)
.then(f, f)
가 다른점finally
핸들러엔 인수가 없다. finally
에선 프라미스가 이행되었는지, 거부되었는지 알 수 없고 절차를 마무리하는 동작을 수행하므로 성공 실패 여부를 몰라도 된다finally
핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다new Promise((resolve, reject) => {
setTimeout(() => resolve("결과"), 2000)
})
.finally(() => alert("프라미스가 준비되었습니다."))
.then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음
result가 finally
를 거쳐 then
까지 전달되는 것을 확인 할 수 있다
finally
는 프라미스 결과를 처리하기 위해 만들어진게 아니다
3. .finally(f)
는 함수 f
를 중복해서 쓸 필요가 없기 때문에 .then(f, f)
보다 문법 측면에서 더 편리하다
resolve
나 reject
함수를 호출하는데 이 때 프라미스 객체의 상태가 변화한다