Promise
개요
computer science 에서는 보통 동기 프로그램을 작성할 때 future, promise, delay, deferred 로 아직 실행되지 않은 구문을 작성한다. 웹에서 요청과 응답이 많아 지면서, 동기 프로그램을 작성해야 하는 상황이 많이 발생하였다. 이를 해결하기 위해 ECMAScript6 부터 Promise 패턴이 추가되었다.
Promise는 3가지 상태가 존재한다.
- pending : 대기상태
- fulfilled : 성공상태
- rejected : 실패상태
// pending 대기상태
var pending = new Promise((resolve) => {});
console.log(pending);
// pending
// [[PromiseStatus]]: "pending"
// [[PromiseValue]]: undefined
// fulfilled 성공상태
var fulfilled = new Promise((resolve) => resolve('fulfilled'))
console.log(fulfilled);
// resolved
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: "fulfilled"
// rejected 실패상태
var rejected = new Promise((resolve, reject) => {
throw new Error('rejected');
reject();
});
console.log(rejected);
// rejected
// [[PromiseStatus]]: "rejected"
// [[PromiseValue]]: Error: rejected at <anonymous>
var catchError = new Promise((resolve, reject) => {
throw new Error('rejected')
}).catch(() => { return 'catchValue' })
console.log(catchError);
// pending
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: "catchValue"
var catchThen = new Promise((resolve, reject) => {
reject('rejected')
}).then(null, (error) => { return 'catchThen' }
).catch(() => { return 'catchValue' });
console.log(catchThen);
// pending
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: "catchThen"
Promise
Promise는 생성자를 이용해서 생성할 수 있으며, 인자로 반드시 function을 받아야 한다.
new Promise (function ( resolve, reject ) {});
인자를 받지 않거나 함수 이외의 값을 전달하면, Uncaught TypeError가 발생한다. 만약 인자로 전달된 resolve 와 reject 중 하나라도 호출하지 않는다면, 영원히 pending 상태로 대기한다.
new Promise()
// Uncaught TypeError: Promise resolver undefined is not a function
new Promise('')
// Uncaught TypeError: Promise resolver is not a function
new Promise(1)
// Uncaught TypeError: Promise resolver 1 is not a function
new Promise({})
// Uncaught TypeError: Promise resolver #<Object> is not a function
new Promise(null)
// Uncaught TypeError: Promise resolver null is not a function
new Promise(true)
// Uncaught TypeError: Promise resolver true is not a function
Then
Promise.prototype.then은 promise의 resolve결과를 chaining으로 연속적인 처리가 가능하다. 인자로 2개의 함수 (resolve, reject)를 받을 수 있으며, 반환자는 Promise이다. 때문에 반환 받은 promise를 다시 then으로 chaining할 수 있다.
promise.then(function(value){fulfilled}, function(error){rejected})
만약 return이 없다면, 다음 then은 value를 undefined를 가진 promise를 전달받는다.
항상 새로운 promise를 받기 때문에 이전 promise의 값은 영향을 받지 않는다.
var promise = new Promise(function (resolve, reject) {
resolve(10);
});
promise.then(function (value) {
console.log(value); // 10
}, function (reason) {
console.log(reason);
}).then(Undefined => {
console.log(Undefined); // undefined
return 2;
}).then(two => {
return two + 3;
}).then(five => {
console.log(five); // 5
});
promise.then(function (value) {
console.log(value); // 10
});
만약 참조 형을 전달하고, 중간에 참조 형의 프로퍼티를 변경할 경우 원 promise의 resolve값이 영향을 받는다.
var promise = new Promise(function (resolve, reject) {
resolve({a: 10});
});
promise.then(function (obj) {
console.log(obj); // { a: 10 }
return obj
}).then((obj) => {
obj.b = 20;
return obj;
}).then((obj) => {
console.log(obj); // {a: 10, b: 20}
});
promise.then(function (obj) {
console.log(obj); // {a: 10, b: 20}
});
Catch
Promise.prototype.catch은 promise의 reject나 throw Error의 값을 받는다. 중간의 then에서 onRejected를 처리한다면, catch는 무시된다.
var promise = new Promise(function (resolve, reject) {
resolve(1);
});
promise.then(function () {
return 10;
}).catch(function () {
return 20;
}).then(function (v) {
console.log(v)
});
// 10;
promise.then(one => {
throw new Error('a');
}).then(v => {
console.log(v);
return v
}).catch(() => {
return 2;
}).then(two => {
console.log(two);
return 3
}).then(three => {
throw new Error('three');
}).then((v) => {
console.log(v);
return 4;
}, (threeError) => {
console.log(threeError.message);
return 5;
}).then(five => {
console.log(five);
})
// 2
// three
// 5
return promise
만약 then의 return이 promise라면 resolve, reject의 처리 결과를 반환한다. then은 전달하는 값이 promise가 아니라면 promise로 래핑해서 반환한다. 반환 값이 promise라면 해당 promise를 반환한다.
var promise = new Promise(function (resolve, reject) {
resolve(1);
});
promise.then(function () {
return new Promise((resolve) => {resolve(2);}).then(two => two + 3);
}).then((five) => {
console.log(five);
})
// 5
promise.then(function () {
return new Promise((resolve, reject) => {
reject(4);
}).then(two => two + 3);
}).then((five) => {
console.log(five);
}, (four) => {
console.log(four);
});
// 4
Promise 메서드
Promise.reject와 Promise.resolve는 즉시 실행되는 Promise를 만들 수 있다. then 으로 체이닝 할 경우, resolve는 onFulfilled영역을 reject는 onRejected영역이 호출된다.
Promise.resolve(10).then(v => {
console.log(v);
})
// 10
Promise.reject(new Error('message')).catch(err => {
console.log(err.message);
})
// message
new Promise((resolve) => {
resolve(10);
}).then(function (ten) {
return Promise.resolve(ten + 20);
}).then((thirty) => {
console.log(thirty);
});
// 30
Promise.all
Promise.all은 모든 Promise가 resolve된 이후의 resolve를 배열로 반환한다. 만약 주어진 Promise 중 하나의 Promise라도 reject이 발생된다면, 가장 먼저 reject된 값을 reject으로 반환한다.
Promise.all은 iterable값을 인자로 받기 때문에, Promise가 아니더라도 래핑해서 Promise로 처리한다.
Promise.all([1, 2, 3]).then(console.log)
// [1, 2, 3]
Promise.all('123').then(console.log)
// ["1", "2", "3"]
Promise.all([
Promise.resolve(1),
Promise.reject(2).catch(v => v),
Promise.resolve(3)
]).then(console.log)
// [1, 2, 3]
Promise.all([
new Promise((resolve, reject) => {setTimeout(() => {resolve(1)}, 4000)}),
new Promise((resolve, reject) => {setTimeout(() => {resolve(2)}, 2000)}),
new Promise((resolve, reject) => {setTimeout(() => {reject(3)}, 3000)}),
new Promise((resolve, reject) => {setTimeout(() => {resolve(4)}, 5000)}),
new Promise((resolve, reject) => {setTimeout(() => {reject(5)}, 1000)}),
])
.then(list => {console.log(list);})
.catch(e => console.log(e));
// 5
Promise.race
전달된 Promise들 중 가장 빨리 resolve나 resolve된 값을 반환한다.
promise.race는 iterable값을 인자로 받기 때문에, Promise가 아니더라도 래핑해서 Promise로 처리한다. 하지만 그럴 경우 즉시 반환되기 때문에 race를 쓰는 의미가 없어진다.
Promise.race([
new Promise((resolve, reject) => { setTimeout(() =>{ resolve(1) }, 2000)}),
new Promise((resolve, reject) => { setTimeout(() =>{ resolve(2) }, 1000)}),
new Promise((resolve, reject) => { setTimeout(() =>{ reject(3) }, 4000)}),
new Promise((resolve, reject) => { setTimeout(() =>{ resolve(4) }, 3000)}),
new Promise((resolve, reject) => { setTimeout(() =>{ reject(5) }, 5000)}),
])
.then(v => { console.log(v); })
.catch(e => console.log(e));
Promise 스케줄
Javascript의 Event loop는 Call stack과 Queue (task queue, microtask queue) 사이의 작업들을 확인하여 처리한다.
promise의 then작업들은 microtask queue에 등록되어 처리된다.
- 작업을 만나면 call stack에 넣는다.
- call stack에서 작업을 꺼내고 처리한다.
- 처리 중 callback을 만나면, task queue에 등록한다.
- promise를 만나면, call stack에 넣는다.
- call stack에서 promise 작업을 꺼내고 처리한다.
- 이때 then을 만나면, microtask queue에 등록한다.
- 모든 스크립트의 call stack 작업이 끝나면, 우선적으로, microtask queue를 확인하고 call stack에 넣는다.
- call stack에서 작업을 꺼내고 처리한다.
- microtask queue 작업이 없다면, task queue를 확인하고 Call stack에 넣는다.
console.log('script start');
setTimeout(() => {
console.log('setTimeout')
}, 0);
new Promise((res) => {
console.log('promise0');
res()
console.log('promise1');
}).then(function () {
setTimeout(() => {
console.log('setTimeout2')
}, 0);
}).then(function () {
console.log('promise2');
});
console.log('script end');
// script start
// promise0
// promise1
// script end
// promise2
// ---
// setTimeout
'TIL > JavaScript' 카테고리의 다른 글
[ECMASciprt6+ Features] 6. import, export (0) | 2021.07.22 |
---|---|
[ECMASciprt6+ Features] 5. Arrow Functions (0) | 2021.07.22 |
[ECMASciprt6+ Features] 4. let & const (0) | 2021.07.22 |
[ECMASciprt6+ Features] 3. Class (0) | 2021.07.21 |
[ECMAScript6+를 위한 보충학습] 2. Callback (0) | 2021.07.21 |