티스토리 뷰
Promise는 해석하면 '약속'
즉, 비동기 처리에 대해, 처리가 끝나면 알려달라는 약속입니다.
비동기 처리
• 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성
자바 스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백함수를 사용하였지만
에러의 처리가 곤란하고 콜백지옥으로 인해 가독성도 떨어지고
여러개의 비동기 처리를 한번에 처리하는데 한계가 있었습니다.
Promise는 이러한 비동기 처리를 위한 객체이며 전통적인 콜백 패턴이 가진 단점을 보완하고
비동기 처리의 성공 또는 실패와 같은 최종결과를 명확하게 표현할수가 있습니다.
Promise의 기본 구조
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행
});
Promise의 3가지 상태(states)
1. 대기(Pending) : 비동기 처리로직이 아직 완료되지 않은 상태
■ new Promise()메서드를 호출하게 되면 대기(Pending) 상태가 됩니다.
■ new Promise()메서드를 호출 할때 콜백 함수를 선언할수 있고 콜백 함수의 인자는 resolve와 reject입니다.
new Promise(function(resolve, reject) { });
2. 이행(Fulfilled) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
■ 콜백함수의 인자인 resolve();를 실행하게 되면 이행(Fulfilled) 상태가 됩니다.
new Promise(function(resolve, reject) { resolve(); });
■ 이행 상태가 되었다면 .then()메서드를 사용하여 성공한 경우의 처리 결과 값을 받을수가 있습니다.
.then()메서드는 새로운 Promise를 반환하여 연쇄적으로 호출을 할 수 있습니다.
function getData() { return new Promise(function(resolve, reject) { var data = 100; resolve(data); }); } // resolve()의 결과 값 data를 resolvedData로 받음 getData().then(function(resolvedData) { console.log(resolvedData); // 100 });
3. 거부(Rejected) : 비동치 처리가 실패하거나 오류가 발생한 상태
■ 콜백함수의 인자인 reject();를 실행하게 되면 거부(Rejected) 상태가 됩니다.
new Promise(function(resolve, reject) { reject(); });
■ 거부 상태가 되었다면 .catch()메서드를 사용하여 실패한 경우의 처리를 정의합니다.
.catch()메서드는 새로운 Promise를 반환하여 연쇄적으로 호출을 할 수 있습니다.
function getData() { return new Promise(function(resolve, reject) { reject(new Error("Request is failed")); }); } // reject()의 결과 값 Error를 err에 받음 getData().then().catch(function(err) { console.log(err); // Error: Request is failed });
프로미스 연결하기
콜백지옥의 커피메뉴
setTimeout(
function (name) {
var coffeeList = name;
console.log(coffeeList);
setTimeout(
function (name) {
coffeeList += ", " + name;
console.log(coffeeList);
setTimeout(
function (name) {
coffeeList += ", " + name;
console.log(coffeeList);
setTimeout(
function (name) {
coffeeList += ", " + name;
console.log(coffeeList);
},
500,
"카페라떼"
);
},
500,
"카페모카"
);
},
500,
"아메리카노"
);
},
500,
"에스프레소"
);
위와 같은 콜백지옥 함수를 Primise를 사용하여 비동기 → 동기적 표현을 구현 할 수가 있습니다.
Promise의 특징으로는 .then메서드를 호출하고 나면 새로운 Promise객체가 반환 되기 때문에 여러개의 프로미스를 연결하여 사용할 수 있습니다.
new Promise(function (resolve) {
setTimeout(function () {
var name = '에스프레소';
console.log(name);
resolve(name);
}, 500);
}).then(function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ', 아메리카노';
console.log(name);
resolve(name);
}, 500);
});
}).then(function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ', 카페모카';
console.log(name);
resolve(name);
}, 500);
});
}).then(function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var name = prevName + ', 카페라떼';
console.log(name);
resolve(name);
}, 500);
});
});
Promise의 매개변수는 resolve, reject 두개가 들어갑니다.
이 두 매개변수는 Promise의 최종 상태를 결정하는데 사용되는데
resolve()가 호출되면 Promise가 대기 상태에서 이행 상태로 넘어가기 때문에 첫번째의 .then()로직으로 넘어갑니다.
resolve()안에 있던 name값은 .then()메서드 콜백함수의 매개변수 preName으로 전달 됩니다.
첫 new Promise에서 return값이 없는데 다음 .then()으로 매개변수가 전달 될수 있는 이유는
resolve함수가 Promise를 완료할 때, 해당값이 Promise의 이행 값으로 설정 되기 때문입니다.
또한 reject()함수는 Promise가 거부될 때 호출되며, 거부의 원인이 되는 에러를 인자로 .catch블록의 콜백함수로 전달합니다.
new Promise앞에 return을 사용한 이유는 Promise체이닝을 통해 비동기 작업을 순차적으로 연결하고 관리하기 위함입니다.
1. 첫번 째 Primise가 이행되면(첫 번째 비동기 작업이 완료되면) .then()의 콜백함수가 실행됩니다.
2. 그 콜백함수 내부에서 새로운 Promise를 생성하여 두 번째 비동기 작업을 수행합니다.
3. 두번째 Promise는 두번째 비동기 작업의 완료 여부를 나타내고, 이행되면 그 결과를 가지고 다음 .then블록이 실행됩니다.
이렇게 name에는 에스프레소부터 카페라떼까지 쌓여가면서 출력되게 됩니다.
비동기 작업의 동기적 표현
연결한 프로미스 함수화 하기
위 코드를 보면 같은 내용의 로직들이 계속해서 반복되는것을 알수있습니다.
[ 함수 : 재사용이 가능한 블록, 코드의 재사용성과 가독성을 향상 ]
즉, 같은 로직을 함수화 할수가 있다는 것입니다.
var addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
var newName = prevName ? (prevName + ', ' + name) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
위 코드는 Promise 체이닝을 통해 비동기 작업을 순차적으로 수행합니다.
1. addCoffee('에스프레소')()는 즉시 실행 함수(IIFE)로 해당 함수의 리턴값을 반환한뒤 리턴으로 온 함수를 실행 시킵니다.
2. 클로저 라는 개념으로 인해 newName은 에스프레소를 저장할수 있습니다.
3. 새로운 new Promise객체를 반환하기 때문에 .then으로 해당 과정을 반복하면 원하는 출력 값이 나올수 있습니다.
Generator, 이터러블 객체(Iterable)
*가 붙은 함수는 Generator 함수입니다.
Generator 함수는 실행하면 Iterator객체가 반환됩니다.
Generator는 함수 실행을 중간에 일시 중단하고 다시 시작할수 있는 특수한 종류의 함수입니다.
아래는 Generator 함수와 yield키워드를 사용하여 비동기적인 작업을 순차적으로 진행하는 코드입니다.
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
var coffeeGenerator = function* () {
var espresso = yield addCoffee('', '에스프레소');
console.log(espresso);
var americano = yield addCoffee(espresso, '아메리카노');
console.log(americano);
var mocha = yield addCoffee(americano, '카페모카');
console.log(mocha);
var latte = yield addCoffee(mocha, '카페라떼');
console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
Iterator객체는 next메서드로 순환 할수 있으며, next메서드 호출시 Generator함수 내부에서 가장 먼저 등장하는 yield에서 stop한 다음 다시 next메서드를 호출하면 멈췄던 부분에서 그 다음 yield까지 실행후 stop합니다.
즉, 비동기 작업이 완료되는 시점마다 next메서드를 호출해주면 Generator함수 내부소스가 위 → 아래 순차적으로 진행됩니다.
• next()
- JavaScript에서 Generator객체에서 사용되는 메서드로 Generator함수의 실행을 제어하는 데 사용됩니다.
- next메서드 호출시 Generator함수 내부에서 yield 표현식까지 실행되며 해당 표현식의 평가 결과를 가진 객체가 반환됩니다.
- next()를 호출할 때 인자를 전달하면, Generator함수 내에서 마지막으로 실행된 yield표현식에 해당 값이 할당 됩니다.
작동순서
1. coffeeMaker.next()가 호출되면 Generator함수 coffeeGenerator가 실행됩니다.
2. yield 표현식까지 실행되어 addCoffee함수가 호출되고 해당 비동기 작업이 시작됩니다.
3. setTime작업이 끝난뒤 addCoffee함수 내부에서 coffeeMaker.next()가 호출되어 Generator함수를 재개합니다.
4. 이때 Generator함수는 이전 yield표현식에서 yield한 값을 받아오며 그 값이 이전 addCoffee함수의 결과입니다.
5. 이과정이 반복되면서 결과가 순차적으로 출력됩니다.
Promise + Async/await
async 및 await를 사용하여 Promise를 기반으로 하는 비동기 작업을 좀 더 간편하게 처리 하는 방법입니다.
비동기 작업을 수행하고자 함수 앞에 async를 붙이고
함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 됩니다.
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function(){
resolve(name);
}, 500);
});
};
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
• async
- 해당 키워드를 함수 앞에 붙이면 해당 함수는 항상 Promise를 반환합니다.
- 내부에서 await키워드를 사용하여 Promise가 처리될 때까지 기다릴수 있습니다.
• await
- await는 async 함수 내에서만 사용 가능합니다.
- await뒤에는 항상 Promise가 완료될 때까지 기다리고자 하는 표현식이 위치합니다.
- await표현식은 그 결과를 반환하며, 만약 Promise가 실패하면 예외를 던집니다.
작동순서
1. coffeeMaker함수를 호출하게 되면 async키워드로 선언되어 있고 비동기적으로 작업을 수행하게 됩니다.
2. await _addCoffee('에스프레소') 호출을 하면 _addCoffee가 호출되고 함수내의 await addCoffee(name)을 통해 값이 넘어 오길 기다립니다.
3. resolve(name)이 호출되어 Promise가 완료되면 await addCoffee(name)이 해결되고, 해당 결과로 '에스프레소'가 반환됩니다.
4. _addCoffee 함수 내에서 이를 coffeeList에 추가하고 해당과정을 반복하여 결과를 출력합니다.
이렇게 Promise와 비동기 작업의 동기적 표현을 알아봤습니다.
Promise와 비동기가 워낙 어려워 양이 좀 많긴 하지만 그래도 이렇게 정리하여
조금 더 Promise와 가까워질수 있게 된것 같습니다.
배워나가는 코린이 입니다!!
부족한게 있다면 댓글로 지적해주세요!! 감사합니다!😊
참고 : captain pangyo
'프로그래밍 기초 > JavaScript' 카테고리의 다른 글
비동기처리란? (0) | 2024.02.16 |
---|---|
클로저란? (0) | 2024.01.11 |
TIL 9일차 + 콜백 함수란? (2) | 2024.01.04 |
TIL 8일차 + 실행 컨텍스트란? (1) | 2024.01.03 |
TIL 7일차 + 배열이란? (2) | 2024.01.02 |