-
비동기 프로그래밍에 관하여 - 2. 프로미스(Promise)front-end/ES6 2022. 2. 21. 10:23728x90
promise란 한국말로는 약속이다. 뜬금없이 무엇을 약속한다는 것일까?
MDN에 정의된 promise의 설명은 다음과 같다.
- Promise는 프로미스가 생성된 시점에는 알려지지 않을수도 있는 값을 위한 대리자이다.
- 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있다.
- 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 갑슬 반환할 수 있다.
- 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에서 결과를 제공하겠다는 '약속(promise)'를 반환한다.
promise는 자바스크립트에서 제공하는, 비동기를 간편하게 처리할 수 있도록 도와주는 '오브젝트'이다.
promise는 정해진 장시간의 기능을 수행하고 나서
정상적으로 기능이 수행되었다면, 성공 메세지와 함께 처리된 결과 값을 전달해주고,
만약 에러가 발생한다면 에러 메세지와 함께 에러 정보를 전달해준다.
예를 들어보자,
만약 무엇인가 사전 예약을 통해 가입했다고 가정하자(강의나, 게임이나 등등).
여기서 사전예약을 한 시점에서 promise가 만들어졌다고 볼 수 있다.
그리고 장시간이 지나 비로소 성공적으로 오픈이 되면 메일로 공지가 오게 되고, promise가 성공적으로 값을 전달했다고 볼 수 있다.
그리고 누군가 정식 오픈이 된 후 가입했다고 가정하자.
그러면 이미 성공적으로 처리가 된 promise이기 때문에 대기 시간없이 바로 메일로 공지가 가게 된다.
promise에서 집중할 포인트는 크게 두가지 이다.
첫번 째는 state(상태)이다. promise는 다음 중 하나의 상태를 가진다.
- 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
- 이행(fulfilled): 연산이 성공적으로 완료됨.
- 거부(rejected): 연산이 실패함.
두번 째는 producer와 comsumer의 차이점을 아는 것이다.
우리가 원하는 기능을 수행해서 해당하는 데이터를 만들어 내는 Producer(즉, promise object)와
원하는 데이터를 사용하는 Comsumer로 나누어진다.
1. Producer
우리가 원하는 기능을 비동기적으로 수행하는 promise를 만들어보자.
Promise는 클래스이기 때문에 new 키워드를 통해 oject를 생성한다.
Promise의 생성자를 확인해보면 먼저 excutor라는 콜백함수를 전달해 주어야 한다.
그리고 이 콜백함수는 또 다른 두가지의 콜백함수를 가진다.
바로 resolve와 reject이다.
resolve는 기능을 정상적으로 수행한 후 최종 데이터를 전달한다.
reject는 기능을 수행하다가 문제가 생기면 호출된다.
const promise = new Promise((resolve, reject) => { //doing some heavy work() console.log("doing somthing..."); });
보통 promise는 서버 통신과 같은 무거운 로직을 비동기적으로 처리할 때 사용된다.
시간이 걸리는 작업을 동기적으로 처리를 하게 된다면 시간이 계속 지연되기 때문에 비동기 로직으로 병렬적 처리를 하는것이다.
promise를 만드는 순간 우리가 전달한 excutor 콜백 함수가 바로 실행된다.
promise가 만들어지는 순간 excutor가 실행된다는 사실을 염두하고 코드를 작성해야한다. 만약 promise안에 네트워크 통신을 하는 코드를 적었다면 promise가 만들어지는 순간 네트워크 통신을 수행하게된다.
이 사실을 간과하고 있다가 원하지 않은 네트워크 통신을 불필요하게 수행하는 경우가 발생할 수 있다.
다시 코드로 넘어가서,
만약 promise를 통해서 성공적으로 원하는 값을 가져왔다면 resolve() 콜백함수를 호출하면된다.
반환받은 데이터를 원하는 모습으로 가공하여 resolve를 통해 전달하면된다.
const promise = new Promise((resolve, reject) => { //doing some heavy work() console.log("doing somthing..."); setTimeout(() => { resolve('success') },2000) }); //서버와 통신하는듯한 상황을 위해 setTimeout()으로 딜레이를 주었다.
이렇게 promise 객체로 전달받은 값은 어떻게 사용할 수 있을까?
바로 then, catch, finally와 같은 Consumer을 통해 핸들링할 수 있다.
const promise = new Promise((resolve, reject) => { //doing some heavy work() console.log("doing somthing..."); setTimeout(() => { resolve("success"); }, 2000); }); promise.then((value) => { console.log(value); //value 값은 resolve를 통해 받아온다. });
then은 promise가 정상적으로 잘 수행이 되고 resolve 콜백함수를 통해 전달한 값(여기서는 "success")을 받아온다.
만약 정상적 수행을 못하고 에러가 발생하게 되면 어떻게 처리해야할까?
그때는 reject 콜백함수를 통해 에러를 처리한다.
const promise = new Promise((resolve, reject) => { //doing some heavy work() console.log("doing somthing..."); setTimeout(() => { //resolve("success"); reject(new Error("no network")); }, 2000); }); //2. Consumers: then, catch, finally promise.then((value) => { console.log(value); });
잡히지 않은 에러(Uncaught Error) 발생 위의 코드는 성공적인 결과를 위한 로직만을 작성했기 때문에 에러가 발생하였을 때 잡히지 않는다는 메세지를 보여준다.
이와 같이 에러가 발생하였을 때는 catch 메소드를 통해 에러를 잡아준다.
const promise = new Promise((resolve, reject) => { //doing some heavy work() console.log("doing somthing..."); setTimeout(() => { //resolve("success"); reject(new Error("no network")); }, 2000); }); promise .then((value) => { console.log(value); }) .catch((error) => { console.log(error); }) .finally(() => { console.log("finally"); });
에러를 catch하였다. promise의 then()을 호출하게 되면 promise를 리턴한다.
그렇기 때문에 promise.then(){...}.catch() 와 같은 코드를 작성하여도 promise에서 catch를 호출한 것과 다름이 없기 때문에 문제가 없는 것이다.
promise 사용을 정리하자면 다음과 같다.
- promise 객체를 생성하고 비동기적으로 수행할 코드를 각각 resolve와 reject를 통해 전달한다.
- then()과 catch()를 이용하여 각각의 결과를 받아온다.
- finally를 사용하게 되면 결과와 상관없이 마지막에 finally에 작성된 코드가 실행된다.
이전 포스팅의 콜백 지옥 함수를 기억하는가
class UserStorage { loginUser(id, password, onSuccess, onError) { setTimeout(() => { if ( (id === "user" && password === "user") || (id === "admin" && password === "admin") ) { onSuccess(id); } else { onError(new Error("not found")); } }, 2000); } getRoles(user, onSuccess, onError) { setTimeout(() => { if (user === "admin") { onSuccess({ name: "admin", role: "admin" }); } else { onError(new Error("no access")); } }, 1000); } } const userStorage = new UserStorage(); const id = prompt("enter user id"); const password = prompt("enter user password"); userStorage.loginUser( id, password, (userSuccess) => { userStorage.getRoles( userSuccess, (userRole) => { alert(`hello ${userRole.name}, you have a ${userRole.role} role`); }, (error) => { console.log(error); } ); }, (error) => { console.log(error); } );
위와 같은 코드의 콜백 지옥은 Promise를 활용하여 깔끔하게 정리할 수 있다.
class UserStorage { loginUser(id, password) { return new Promise((resolve, reject) => { setTimeout(() => { if ( (id === "user" && password === "user") || (id === "admin" && password === "admin") ) { resolve(id); } else { reject(new Error("not found")); } }, 2000); }); } getRoles(user) { return new Promise((resolve, reject) => { setTimeout(() => { if (user === "admin") { resolve({ name: "admin", role: "admin" }); } else { reject(new Error("no access")); } }, 1000); }); } } const userStorage = new UserStorage(); const id = prompt("enter user id"); const password = prompt("enter user password"); userStorage .loginUser(id, password) .then(userStorage.getRoles) .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`)) .catch(console.log);
결과값을 반환받은 로직은 Promise 객체를 생성하여 처리를 하였고,
반환받은 값을 콜백으로 넘기며 사용하던 코드는 체이닝을 활용하여 깔끔하게 정리된 모습이다.
728x90'front-end > ES6' 카테고리의 다른 글
개체 참조 및 복사 (0) 2022.02.21 비동기 프로그래밍에 관하여 - 3. async / await (0) 2022.02.21 비동기 프로그래밍에 관하여 - 1. 콜백(Callback) (0) 2022.02.21 화살표 함수(Arrow Function) (0) 2022.02.21 스코프에 관하여 (var, let, const) (0) 2022.02.21