-
NodeJS - NodeJS 실행을 위한 JavaScript 문법2개발언어/JavaScript 2022. 12. 26. 18:29
Promise
Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
Promise는 프로미스가 생성된 시점에는 알수 없을 지도 모르는 값의 대리자로 비동기 연산이 종료된 이후에 결과값 과 실패 사유를 처리하기 위한 처리기를 연결할 수 있다.
프로미스를 사용하면 비동기 메서드를 마치 동기 메서드가 반환하듯 값을 반환할 수 있다. 다만, 최종결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'을 반환한다.
프로미스는 다음중 하나의 상태를 가진다.
프로미스는 다음과 같은 규칙이 있다 먼저 프로미스 객체를 생성해야 한다.
const condition = true; // true며 resolve, false면 reject const promise = new Promise((resolve, reject) => { if (condition) { resolve("성공"); } else { reject("실패"); } }); // 다른 코드가 들어갈 수 있음 promise .then((message) => { console.log(message); //성공(resolve)한 경우 실행 }) .catch((error) => { console.error(error); //실패(reject)한 경우 실행 }) .finally(() => { // 끝나고 무조건 실행 console.log("무조건"); });
new Promise로 프로미스를 생성할 수 있으며, 그 내부에 resolve와 reject를 매개변수로 갖는 콜백 함수를 넣는다. 이렇게 만든 promise 변수에 then과 catch 메서드를 붙일 수 있다.
프로미스 내부에서 resolve가 호출됨녀 then이 실행되고, reject가 호출되면 catch가 실행된다. finally 부분은 성공/실패 여부와 상관 없이 실행된다.
resolve와 reject에 넣어준 인수는 각각 then과 catch의 매개변수에서 받을 수 있다. 즉, resolve('성공')이 호출되면 then의 message가 '성공'이 된다. 만약 reject('실패')가 호출되면 catch의 error가 '실패'가 되는 것이다.
condition 변수를 false로 바꿔보면 catch에서 에러가 로깅된다.
프로미스를 쉽게 설명하면, 실행은 바로 하되 결과값은 나중에 받는 객체이다. 결과 값은 실행이 완료된 후 then이나 catch메서드를 통해 받는다.
위 예제에서 new Promise와 promise.then 사이에 다른 코드가 들어갈 수 있다. new Promise는 바로 실행 되지만, 결과값은 then을 붙였을때 받게 된다.
then이나 catch사이에서 다시 다른 then이나 catch를 붙일 수 있다. 이전 then의 return값을 다음 then 매개변수로 넘긴다. 프로미스를 return한 경우에 프로미스가 수행된 후 다음 then이나 catch가 호출된다.
const condition = true; // true며 resolve, false면 reject const promise = new Promise((resolve, reject) => { if (condition) { resolve("성공"); } else { reject("실패"); } }); // 다른 코드가 들어갈 수 있음 promise .then((message) => { return new Promise((resolve, reject) => { resolve(message); }); }) .then((message2) => { console.log(message2); return new Promise((resolve, reject) => { resolve(message2); }); }) .then((message3) => { console.log(message3); }) .catch((error) => { console.error(error); //실패(reject)한 경우 실행 }) .finally(() => { // 끝나고 무조건 실행 console.log("무조건"); });
처음 then에서 message를 resolve하면 다음 then에서 message2로 받을 수 있다. 여기서 다시 message2를 resolve한 것을 다음 the에서 message3으로 받았다. 단, then에서 new Promise를 return해야 다음 then에서 받을 수 있다.
콜백을 프로미스로 바꾸기
콜백
function findAndSaveUser(Users) { Users.findOne({}, (err, user) => { // 첫 번째 콜백 if (err) { return console.error(err); } user.name = "zero"; user.save((err) => { // 두 번째 콜백 if (err) { return console.error(err); } Users.findOne({ gender: "m" }, (err, user) => { // 세 번째 콜백 //생략 }); }); }); }
프로미스
function findAndSaveUser(Users) { Users.findOne({}) .then((user) => { user.name = "zero"; return user.save(); }) .then((user) => { return Users.findOne({ gender: "m" }); }) .then((user) => { //생략 }) .catch((err) => { console.log(err); }); }
1. findAndSaveUser()메서드에 Users 객체를 전달한다.
2. Users의 메서드 findOne()메서드에 빈 객체'{}'를 전달한다. 이후 findOne메서드가 실행되고 실행결과로 error가 발생한 경우 catch를 통해 console.error(err)를 반환된다.실행결과에 문제가 없을 경우 findOne()메서드가 반환하는 결과(객체일것으로 예상) user 가되고 user객체의 name 속성은 'zero'가 된다. 그 후 user의 메서드 save를 이용해서 변경사항을 전달한 후 save 결과를 return 한다.
2. save가 정상적으로 이루어지지 않아 error를 반환 경우 catch를 통해 console.error(err)를 반환된다. 제대로 저장이되었을 경우 저장된 후에 다음 코드가 실행된다.
프로미스를 사용하면 깊이가 세 단계 이상 깊어지지 않는다. 위 코드에서 then 메서드들은 순차적으로 실행되고, 콜백에서 매번따로 처리해야 했던 에러도 마지막 catch에서 한번에 처리 할 수 있다.
하지만 모든 콜백 함수를 위화 같이 바꿀수 있는건 아니다. 메서드가 프로미스 방식을 지원해야 한다. 예제 코드는 findOne과 save메서드가 내부적으로 프로미스 객체를 가지고 있다고 가정했기에 가능하다.
Promise. resolve()
const promise1 = Promise.resolve(123); promise1.then((value) => { console.log(value); // expected output: 123 });
Promise.all
Promie.all()메서드는 순회 가능한 객체(배열)에 주어진(배열 형태로 주어진)모든 프로미스를 하나의 Promise로 받는다 이때 반환 되어지는 프로미스는 입력받은 모든 값이 fulfilled 상태일때 fulfillment 값이 배열 형태로 전달됩과 함께 이행완료가 된다. 하지만 입력 받은 프로미스 배열 중 하나라도 reject되면 첫번째 reject 이유와 함께 거절된다.
const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "foo"); }); Promise.all([promise1, promise2, promise3]).then((result) => { console.log(result); //[3,42,'foo'] });
프로미스 여러개를 한번에 실행할 수 있는 방법이 있다. 기존에 콜백 패턴이었다면 콜백을 여러 번 중첩해서 사용해야 했을 것이다. 하지만 Promise.all을 활용하면 간단히 해결할 수 았다.
const promise1 = Promise.resolve("성공1"); const promise2 = Promise.resolve("성공2"); Promise.all([promise1, promise2]) .then((result) => { console.log(result); // ['성공1','성공2'] }) .catch((error) => { console.log(error); });
Promise.resolves는 즉시 resolver하는 프로미스를 만드는 방법이다. 비슷한 것으로 즉시 reject하는 Promise.reject도 있다.
async/await
Node.js 7.6 버전 부터 지원되는 기능이다. ES2017에서 추가되었다.
프로미스가 콜백 지옥을 해결했지만, 여전히 코드가 장황하다.then과 catch가 계속 반복되기 때문이다. async/await 문법은 프로미스를 사용한 코드를 한 번더 까끔하게 줄인다.
async function findAndSaveUser(Users) { let user = await Users.findOne({}); user.name = "zero"; user = await user.save(); user = await Users.findOne({ gender: "m" }); //생략 }
이제 함수는 프로미스가 resolve될 때까지 기다린 뒤 다음 로직으로 넘어간다. 예시를 들어 구체적으로 설명하면 await Users.findOne({})이 resolve될 때까지 기다린 다음에 user 변수를 초기화 하는 것이다.
error처리 추가
async function findAndSaveUser(Users) { try { let user = await Users.findOne({}); user.name = "zero"; user = await user.save(); user = await Users.findOne({ gender: "m" }); //생략 } catch (error) { console.log(error); } }
for문과 async/await를 같이 써서 프로미스를 순차적으로 실행할 수 있다. for 문과 함께쓰는 것은 Node.js 10 버전 부터 지원하는 ES 2018 문법이다.
const promise1 = Promise.resolve("성공1"); const promise2 = Promise.resolve("성공2"); (async () => { for await (promise of [promise1, promise2]) { console.log(promise); } })();
for await of 문을 사용해서 프로미스 배열을 순회하는 모습이다. async 함수의 반환값은 항상 Promise로 감싸진다. 따라서 실행 후 then을 붙이거나 또 다른 async함수 안에서 await을 붙여서 처리할 수 있다.
async function findAndSaveUser(User) { //생략 } findAndSaveUser().then(() => { /* 생략 */ }); //또는 async function other() { const result = await findAndSaveUser(); }
출처
모던 JavaScript 튜토리얼 - https://ko.javascript.info/
MDN > 개발자를 위한 웹 기술 > JavaScript > JavaScript 참고서 > 표준 내장 객체 > Promise -
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
Node.js 교과서 - 조현영 저
'개발언어 > JavaScript' 카테고리의 다른 글
JavaScript - nullish coalescing(널 병합)/ Optional Chaining (0) 2023.04.15 JavaScript - 프로토타입 체인 (0) 2022.12.26 JavaScript - 프로토타입 (0) 2022.12.23 JavaScript - 함수 바인딩 (0) 2022.12.23 JavaScript - 메서드와 this (0) 2022.12.22