-
[Javascript] 비동기 - #2. PromiseFront-End(Web)/Javascript 2021. 2. 1. 19:30반응형
나도 그렇고, fetch()-then 이나 async/await 을 사용하면 최초에 Promise 라는 결과값이 반환되는 것을 볼 수 있었을 것이다.
또한, 자세히 보면 이 Promise는 객체(Object) 형태를 하고 있으며, 아래와 같이 어떠한 값들을 포함하고 있었다.
이 Promise는 저번에 알아본 콜백지옥을 대체한다고 했는데, 이에 대해 좀 더 자세히 공부해보았다.
📒 Promise 란?
- Promise가 필요한 이유
1) 콜백지옥
저번 비동기 시간에 얘기한 콜백지옥이다. 보시다시피 가독성도 나쁠 뿐더러, 에러 발생 가능성이 높기 때문에 대안이 필요했던 것이다.
2) 에러 처리의 한계
try { setTimeout(() => { throw new Error('Error!'); }, 1000); } catch (e) { console.log('에러를 캐치하지 못한다..'); console.log(e); }
위 예제로 이해해보았다. 먼저, try 블록은 setTimeout 을 통해 1초 뒤에 에러를 발생시킨다.
하지만, 이 에러는 catch 블록에서 감지하지 못하는데, 이는 저번의 Javascript 엔진원리와 연관지어 생각할 수 있다.
우선, setTimeout 내 에러함수는 비동기이므로, Call Stack에서 Event Queue로 넘어가 대기한다. (Web API에서 setTimeout 제거)
이후, Call Stack의 다른 함수들이 실행되고, Call Stack이 비었음을 확인한 뒤 Event Queue 함수가 넘어온다.
넘어온 함수는, setTimeout 함수 포멧은 없고 caller(throw 에러)만 넘어온 상태이다.
예외처리(exception)는 caller(콜백함수) 방향으로 진행되는데, 위처럼 포멧이 없어졌기에 catch블록이 적용되지 못하는 것이다.
이러한 문제들로 인해, 비동기를 효과적으로 처리할 수 있는 Promise가 ES6에서 등장했고, 대부분의 브라우저가 정식채택 중이다.
- Promise 개념
JavaScript에서 Promise는 비동기적으로 실행하는 작업의 결과를 나타내는 객체이다. 이는 아래와 같은 장점들이 있겠다.
- 비동기 처리의 결과를 객체 형태로 반환한다. 이는 곧, 결과값 외의 다양한 정보를 함축할 수 있다는 것이다.
- 그 중 하나로, 비동기 처리의 상태정보(state)를 포함하고 있으며, 상황에 따른 메서드를 실행할 수 있다. (대기, 수행, 거부 등)
- 또, Promise 객체를 처리하는 메서드들을 지원한다. 이를 통해, 다양한 비동기 처리가 가능해진다. (then, catch(e) 과 같은)
- Promise 상태와 생성
1) Promise 상태 정보(state)
상태 의미 구현 pending 비동기 처리가 아직 수행되지 않은 상태 resolve 또는 reject 함수가 아직 호출되지 않은 상태 fulfilled 비동기 처리가 수행된 상태 (성공) resolve 함수가 호출된 상태 rejected 비동기 처리가 수행된 상태 (실패) reject 함수가 호출된 상태 settled 비동기 처리가 수행된 상태 (성공 또는 실패) resolve 또는 reject 함수가 호출된 상태 이처럼, Promise 객체는 [[PromiseState]] 에 상태값을 포함한다.
이에 따라, Promise 인스턴스가 실행하는 내부 메서드가 달라지게 된다. (resolve, reject)
const pending = new Promise((resolve) => {}); console.log(pending); // pending // [[PromiseStatus]]: "pending" // [[PromiseValue]]: undefined const fulfilled = new Promise((resolve) => resolve('fulfilled')) console.log(fulfilled); // resolved // [[PromiseStatus]]: "resolved" // [[PromiseValue]]: "fulfilled" const rejected = new Promise((resolve, reject) => { throw new Error('rejected'); reject(); }); console.log(rejected); // rejected // [[PromiseStatus]]: "rejected" // [[PromiseValue]]: Error: rejected at <anonymous> const 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"
2) Promise 생성
const promise = new Promise((resolve, reject) => { // 비동기 작업을 수행한다. if (/* 비동기 작업 수행 성공 */) { resolve('result'); } else { /* 비동기 작업 수행 실패 */ reject('failure reason'); } })
위처럼, Promise 객체는 new 키워드와 생성자 함수를 통해 생성(인스턴스화)할 수 있다.
또한, Promise는 매개변수로 executor(실행함수)를 받으며, 이 실행함수는 다시 매개 변수로 두 가지 함수를 받는다.
첫 번째 함수(resolve)는 비동기 작업을 성공적으로 완료해 결과를 값으로 반환할 때 호출되며,
두 번째 함수(reject)는 작업이 실패하여 오류의 원인을 반환할 때 호출된다. 두 번째 함수는 주로 오류 객체를 받는다. (throw 에러)
📒 Promise 메서드
Promise 함수에서 비동기 처리 결과값을 반환하면, 다양한 메서드를 통해 이후처리를 진행할 수 있다.
- then()
Promise의 fulfilled, rejected에 따른 이후처리(콜백함수) 추가하는 메서드이며, 이 콜백함수의 처리값을 반환한다.
Promise 객체를 다룰 때 가장 많이 사용되고 기본이 되는 메서드이다!
promise .then( function(result) { /* 결과(result)를 다룹니다 */ }, function(error) { /* 에러(error)를 다룹니다 */ } );
.then 의 첫 번째 인자는 비동기가 성공(fulfilled) 했을 때, 두 번째 인자는 실패(rejected) 했을 때 콜백함수를 받는다.
* 통상, 두 번째 인자는 Error throw를 처리하는 경우가 많다.
또한, 인자로 받은 콜백함수의 결과값은 마찬가지로 Promise 객체형태로 반환된다. (상태값 포함)
// 성공(resolve) let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("done!"), 1000); }); promise.then( result => alert(result), // 1초 후 "done!"을 출력 error => alert(error) // 실행되지 않음 ); // 실패(reject) let promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("에러 발생!")), 1000); }); promise.then( result => alert(result), // 실행되지 않음 error => alert(error) // 1초 후 "Error: 에러 발생!"를 출력 );
* then() 메서드 사용시, 예외처리 없이 첫 번째 콜백함수만 작성하여 비동기 처리를 하기도 한다.
* then() 의 장점 : Chaining
then() 은 앞의 비동기 처리결과를 객체(상태, 값)로 받아 처리한다는 점에서, 콜백지옥에 비해 가독성이나 안정성이 높아졌다.
// Callback Hell... a(function (resultA) { b(resultA, function (resultB) { c(resultB, function (resultC) { d(resultC, function (resultD) { //OMG..... }); }); }); }); // then Chaning promise.then(function(a){ }).then(function(b){ }).then(function(c){ }).then(function(d){ });
- catch()
Promise 비동기 처리 혹은 then()의 에러처리를 위한 메서드이다. 마찬가지로, Promise 객체를 반환한다.
let promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("에러 발생!")), 1000); }); promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
catch() 메서드는 문법이 간결해진, then(null, [rejectCallback]) 과 같은 기능이라고 생각하면 되겠다!
- finally()
Promise 비동기 처리 혹은 then()의 마무리를 위한 메서드이다. 마찬가지로, Promise 객체를 반환한다.
new Promise((resolve, reject) => { /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve·reject를 호출함 */ }) // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨 .finally(() => 로딩 인디케이터 중지) .then(result => result와 err 보여줌)
finally() 역시 then([commonCallback], [commonCallback]) 과 유사하다고 생각할 수 있으나, 약간의 차이는 있겠다.
- 우선, finally()는 상태값이 필요가 없다. pending을 제외하면, fulfilled / rejected / settled 상관없이 콜백함수를 실행하기 때문
- 반환하는 Promise에 결과값과 에러값 모두를 포함하고 있다. 이를, 다음 then() 처리로 알 수 확인할 수 있다.
- [commonCallback] 함수를 한 번만 적으면 되므로, 문법적으로도 간결하고 비동기 처리종결을 직관적으로 알 수 있다.
- all(iterable)
통상, 인자로 이터러블(배열 등)을 받는다. 배열 안에는 다수의 new Promise 들이 담겨있고, 이를 병렬적으로 처리하는 메서드이다.
모든 Promise 들이 실행되면 결과값(Promise 객체)을 배열로, 중간에 거부되면 거부된 프로미스를 처리한다.
Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(console.log) // [ 1, 2, 3 ] .catch(console.log);
Promise.all() 의 'fulfilled' 경우다. 모든 Promise가 처리된 뒤(3초), 결과값을 iterable로 반환한다.
위처럼, 시간순서가 아닌 iterable(배열) 처리순서를 보장받는다는 장점이 있다.
Promise.all([ new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 1!')), 3000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 2!')), 2000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error('Error 3!')), 1000)) ]).then(console.log) .catch(console.log); // Error: Error 3!
Promise.all() 의 'rejected' 경우다. 3번 Promise의 에러가 출력되며, 비동기가 종료된다.(1초)
- race(iterable)
마찬가지로, 인자로 인자로 이터러블(배열 등)을 받는다. 다수의 new Promise 중, 가장 빨리 처리되는 결과값만 반환하는 메서드다.
Promise.race([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(console.log) // 3 .catch(console.log);
모든 Promise 중, 가장 빠른 3번(1초) Promise 결과값이 반환되었다. 고로, Error의 경우는 Promise.all() 과 동일할 것이다.
- resolve(), reject()
Promise 생성과 동시에, 'fulfilled' 혹은 'rejected' 시 처리할 인자를 입력하는 메서드이다.
// resolve() const resolvedPromise = new Promise(resolve => resolve([1, 2, 3])); resolvedPromise.then(console.log); // [ 1, 2, 3 ] const resolvedPromise = Promise.resolve([1, 2, 3]); resolvedPromise.then(console.log); // [ 1, 2, 3 ] // reject() const rejectedPromise = new Promise((resolve, reject) => reject(new Error('Error!'))); rejectedPromise.catch(console.log); // Error: Error! const rejectedPromise = Promise.reject(new Error('Error!')); rejectedPromise.catch(console.log); // Error: Error!
👊 Callback Hell -> Promise✨ (드림코딩 엘리)
이전 JS공부 때 막연하게 들었던 유튜브 드림코딩 엘리님의 비동기 강의실습을, 완벽한 이해와 함께 복습차원으로 진행했다.
(로그인 로직을 콜백지옥을 만든 뒤, Promise와 Async/Await 으로 수정해가는 좋은 강의이다.)
- Callback Hell
class UserStorage { loginUser(id, pw, onSuccess, onError) { //API1 setTimeout(() => { if ( (id === 'ellie' && pw === 'dream') || (id === 'coder' && pw === 'academy') ) { onSuccess(id); } else { onError(new Error('not found')); } }, 2000); } getRoles(user, onSuccess, onError) { //API2 setTimeout(() => { if (user === 'ellie') { onSuccess({name: 'ellie', role: 'admin'}); } else { onError(new Error('no access')); } }, 1000); } } const userStorage = new UserStorage(); const id = prompt('enter your id'); const pw = prompt('enter your pw') userStorage.loginUser( id, pw, user => { userStorage.getRoles( user, userWithRole => { alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role.`) }, error => {console.log(error)},); }, error => {console.log(error)});
- Promise
class UserStorage { loginUser(id, pw) { return new Promise((resolve, reject) => { setTimeout(() => { if ( (id === 'ellie' && pw === 'dream') || (id === 'coder' && pw === 'academy') ) { resolve(id); } else { reject(new Error('not found')); } }), 2000); }) } getRoles(user) { return new Promise((resolve, reject) => { setTimeout(() => { if (user === 'ellie') { resolve({name: 'ellie', role: 'admin'}); } else { reject(new Error('no access')); } }, 1000); }) } } const userStorage = new UserStorage(); const id = prompt('enter your id'); const pw = prompt('enter your pw') userStorage.loginUser(id, pw) .then(userStorage.getRoles) .then(user => alert(`Hello ${user.name}, you have a ${user.role} role.`)) .catch(console.log);
막연하게 지나쳤던 Promise를 오늘 제대로 공부해보니 재밌기도 했고, 사실 then()이 이것의 메서드인 것도 이번에 알게 되었다.
다음엔, Async/Await 에 대해 포스팅을 하려고 한다. 비동기 처리 및 then() 과 유사한 기능을 수행하지만, 뭔가 장점이 있기에 이를 대체하는 것이리라 생각된다!
[출처]
- MDN Web Docs : developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
- PoiemaWeb : poiemaweb.com/es6-promise
- Ko Javascript Info : ko.javascript.info/promise-basics
- Youtube 드림코딩 엘리(12. Promise 개념 및 활용) : www.youtube.com/watch?v=JB_yU6Oe2eE
[비동기 포스팅 시리즈]
- #1. 비동기 개념과 Callback Function : abangpa1ace.tistory.com/74
- #3. Async/Await : abangpa1ace.tistory.com/83
반응형'Front-End(Web) > Javascript' 카테고리의 다른 글
[Javascript] Prototype(프로토타입) (0) 2021.02.24 [Javascript] 비동기 - #3. Async & Await (0) 2021.02.14 [Javascript] 비동기 - #1. 비동기 개념과 Callback Function (1) 2021.01.30 [Javascript] 2차원 배열 만들기(Array.from()) (0) 2021.01.03 [Javascript] Iteration Protocol (0) 2020.12.17