TIL/JavaScript

[ECMASciprt6+ Features] 7. Promise

Art Rudy
반응형

Promise

 

개요

computer science 에서는 보통 동기 프로그램을 작성할 때 future, promise, delay, deferred 로 아직 실행되지 않은 구문을 작성한다. 웹에서 요청과 응답이 많아 지면서, 동기 프로그램을 작성해야 하는 상황이 많이 발생하였다. 이를 해결하기 위해 ECMAScript6 부터 Promise 패턴이 추가되었다.

 

Promise 3가지 상태가 존재한다.

 

  1. pending : 대기상태
  2. fulfilled : 성공상태
  3. 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 
});

 

만약 참조 형을 전달하고, 중간에 참조 형의 프로퍼티를 변경할 경우 원 promiseresolve값이 영향을 받는다.

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은 모든 Promiseresolve된 이후의 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에 등록되어 처리된다.

 

  1. 작업을 만나면 call stack에 넣는다.
  2. call stack에서 작업을 꺼내고 처리한다.
  3. 처리 중 callback을 만나면, task queue에 등록한다.
  4. promise를 만나면, call stack에 넣는다.
  5. call stack에서 promise 작업을 꺼내고 처리한다.
  6. 이때 then을 만나면, microtask queue에 등록한다.
  7. 모든 스크립트의 call stack 작업이 끝나면, 우선적으로, microtask queue를 확인하고 call stack에 넣는다.
  8. call stack에서 작업을 꺼내고 처리한다.
  9. 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

 

반응형