보고 와야 할 이전글: 콜백 정리 포스팅
Promise 개념
Promise란?
자바스크립트에서 제공하는 비동기를 간편하게 표현할수있는 object
비동기적인것을 수행할 때 콜백함수 대신에 유용하게 쓸 수 있는 object입니다.
Promise는 정해진 장시간의 기능을 수행하고 나서, 정상적으로 기능이 수행이 되어졌다면
성공의 메세지와 함께 처리된 결과값을 전달해줍니다.
만약 수행 중 예상치 못한 문제가 발생했다면 에러를 전달해줍니다.
쉬운 이해를 위해 비유로 예를 들어 설명할 것입니다.
본인을 아주 유명한 가수라고 가정해 봅시다. 그리고 탑 가수인 본인이 밤, 낮으로 다음 싱글 앨범이 언제 나오는지 물어보는 팬들을 상대해야 한다고 해 봅시다.
당신은 앨범이 출시되면 팬들이 자동으로 소식을 받아볼 수 있도록 해 부하를 덜 겁니다.
구독 리스트를 하나 만들어 팬들에게 전달해 이메일 주소를 적게 하고, 앨범이 준비되면 리스트에 있는 팬들에게 메일을 보내 앨범 관련 소식을 바로 받아볼 수 있게 하는 식으로 말이죠. 이렇게 하면 녹음 스튜디오에 화재가 발생해서 출시 예정인 앨범이 취소되는 불상사가 발생해도 관련 소식을 팬들에게 전달 할 수 있습니다.
이제 모두가 행복해졌습니다.
밤낮으로 질문을 하는 팬이 사라졌고, 팬들은 앨범 출시를 놓치지 않을 수 있게 되었으니까요.
이 비유는 코드를 작성하면서 자주 만나는 상황을 실제 일어날 법한 일로 바꾼 것입니다. 바로 아래 같은 상황 말이죠.
- '제작 코드(producing code)'는 원격에서 스크립트를 불러오는 것 같은 시간이 걸리는 일을 합니다. 위 비유에선 '가수’가 제작 코드에 해당합니다.
- '소비 코드(consuming code)'는 '제작 코드’의 결과를 기다렸다가 이를 소비합니다. 이때 소비 주체(함수)는 여럿이 될 수 있습니다. 위 비유에서 소비 코드는 '팬’입니다.
- 프라미스(promise) 는 '제작 코드’와 '소비 코드’를 연결해 주는 특별한 자바스크립트 객체입니다. promise는 위 비유에서 프라미스는 '구독 리스트’입니다. '프라미스’는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어 내는 '제작 코드’가 준비되었을 때, 모든 소비 코드가 결과를 사용할 수 있도록 해줍니다.
promise, producing code
Promise
우리가 원하는 기능을 비동기적으로 실행하는 promise를 만들어봅니다.
let promise = new Promise(function(resolve, reject) {
// executor (제작 코드, '가수')
});
new Promise에 전달되는 함수는 executor(실행자, 실행 함수) 라고 부릅니다.
executor는 new Promise가 만들어질 때 자동으로 실행되는데,
결과를 최종적으로 만들어내는 제작 코드를 포함합니다. 위 비유에서 '가수’가 바로 executor입니다.
executor의 인수 resolve와 reject는 자바스크립트에서 자체 제공하는 콜백입니다.
개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 됩니다.
- resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
- reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
executor는 자동으로 실행되는데 여기서 원하는 일이 처리됩니다.
처리가 끝나면 executor는 처리 성공 여부에 따라 resolve나 reject를 호출합니다.
new Promise 생성자가 반환하는 promise 객체는 다음과 같은 내부 프로퍼티를 갖습니다.
- state — 처음엔 "pending"(보류)이었다 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변합니다.
- result — 처음엔 undefined이었다 resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.
따라서 executor는 위 그림과 같이 promise의 상태를 둘 중 하나로 변화시킵니다.
resolve
자 이제 promise 생성자와 간단한 executor 함수로 만든 예시를 살펴 봅시다.
const promise = new Promise((resolve, reject) =>{
// executor는 new Promise에 의해 자동으로 그리고 즉각적으로 호출됩니다
console.log('무언가 실행함...');
// 2초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 '완료'가 됩니다.
setTimeout(()=>{
resolve('완료');
}, 2000);
});
위 예시를 통해서 알 수 있는 것은 아래와 같습니다.
- executor는 new Promise에 의해 자동으로 그리고 즉각적으로 호출됩니다.
- executor는 인자로 resolve와 reject 함수를 받습니다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 개발자가 따로 만들 필요가 없습니다. 다만, resolve나 reject 중 하나는 반드시 호출해야 합니다.
만약 promise안에 네트워크 통신을 하는 코드를 작성했다면
promise가 만들어지는 그 순간 바로 네트워크 통신을 수행합니다
사용자가 요구했을때만 (사용자가 버튼을 눌렀을때) 네트워크 요청을 해야되는 경우라면,
이런식으로 작성하게 되면 사용자가 요구하지 않았는데, 불필요한 네트워크 통신이 발생하므로 주의해야합니다.
이와 같이 promise를 만드는 순간, 곧바로 자동적으로
그 안에 있는 executer라는 함수가 실행되기 때문에 이점을 유의해 공부해야합니다.
reject
이번에는 executor가 에러와 함께 약속한 작업을 거부하는 경우에 대해 살펴봅시다.
let promise = new Promise(function(resolve, reject) {
// executor는 new Promise에 의해 자동으로 그리고 즉각적으로 호출됩니다
console.log('무언가 실행함...');
// 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다.
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
1초 후 reject(...)가 호출되면 promise의 상태가 "rejected"로 변합니다.
* promise는 성공 또는 실패만 합니다.
executor는 resolve나 reject 중 하나를 반드시 호출해야 합니다.
이때 변경된 상태는 더 이상 변하지 않습니다.
처리가 끝난 프라미스에 resolve와 reject를 호출하면 무시되죠.
let promise = new Promise(function(resolve, reject) {
resolve("완료");
reject(new Error("…")); // 무시됨
setTimeout(() => resolve("…")); // 무시됨
});
여기에 더하여 resolve나 reject는 인수를 하나만 받고(혹은 아무것도 받지 않음)
그 이외의 인수는 무시한다는 특성도 있습니다.
* resolve·reject 함수 즉시 호출하기
executor는 대개 무언가를 비동기적으로 수행하고, 약간의 시간이 지난 후에 resolve, reject를 호출하는데,
꼭 이렇게 할 필요는 없습니다. 아래와 같이 resolve나 reject를 즉시 호출할 수도 있습니다.
let promise = new Promise(function(resolve, reject) {
// 일을 끝마치는 데 시간이 들지 않음
resolve(123); // 결과(123)를 즉시 resolve에 전달함
});
어떤 일을 시작했는데 알고 보니 일이 이미 끝나 저장까지 되어있는 경우,
이렇게 resolve나 reject를 즉시 호출하는 방식을 사용할 수 있습니다.
이렇게 하면 프라미스는 즉시 이행 상태가 됩니다.
consuming code : then, catch, finally
promise 객체는 executor(‘제작 코드’ 혹은 ‘가수’)와 결과나 에러를 받을
소비 함수(‘팬’)를 이어주는 역할을 합니다.
소비함수(‘팬’)는 .then, .catch, .finally 메서드를 사용해 등록(구독)됩니다.
.then()
값이 정상적으로 잘 수행이 된다면, 값을 받아옵니다.
문법은 다음과 같습니다.
promise.then(
function(result) { /* 결과(result)를 다룹니다 */ },
function(error) { /* 에러(error)를 다룹니다 */ }
);
then예시 - promise resolve
아래 예시는 성공적으로 이행된 프라미스에 어떻게 반응하는지 보여줍니다
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("완료!"), 1000);
});
// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
result => alert(result), // 1초 후 "완료!"를 출력
error => alert(error) // 실행되지 않음
);
then - promise reject
프라미스가 거부된 경우에는 아래와 같이 두 번째 함수가 실행됩니다.
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
result => alert(result), // 실행되지 않음
error => alert(error) // 1초 후 "Error: 에러 발생!"을 출력
);
작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 됩니다.
let promise = new Promise(resolve => {
setTimeout(() => resolve("완료!"), 1000);
});
promise.then(alert); // 1초 뒤 "완료!" 출력
.catch()
에러가 발생한 경우만 다루고 싶다면 catch를 사용하면됩니다.
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("에러 발생!")), 1000);
});
// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력
.reject() 는 Error라는 오브젝트를 통해서 값을 전달합니다.
* Error라는 클래스는 자바스크립트에서 제공하는 오브젝트 중 하나입니다.
무언가 에러가 발생했다는 것을 나타내는 오브젝트입니다.
.finally()
성공하든 실패하든 상관없이 무조건 마지막에 어떤 기능을 수행하고 싶을 때 사용합니다.
//예1
new Promise((resolve, reject) => {
setTimeout(() => resolve("결과"), 2000)
})
.finally(() => alert("프라미스가 준비되었습니다.")) //// 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
.then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음 //result와 err 보여줌
//예2
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
})
.finally(() => alert("프라미스가 준비되었습니다."))
.catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음
* 처리된 프라미스의 핸들러는 즉각 실행됩니다.
프라미스가 대기 상태일 때, .then/catch/finally 핸들러는 프라미스가 처리되길 기다립니다.
반면, 프라미스가 이미 처리상태라면 핸들러가 즉각 실행됩니다.
// 아래 프라미스는 생성과 동시에 이행됩니다.
let promise = new Promise(resolve => resolve("완료!"));
promise.then(alert); // 완료! (바로 출력됨)
promise chainnig
promise chaining은 아래와 같이 생겼습니다.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return result * 2;
}).then(function(result) {
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
then은 값을 전달할 수도 있고 promise를 전달 할 수도 있습니다.
위 예시는 아래와 같은 순서로 실행됩니다.
- 1초 후 최초 프라미스가 이행됩니다.
- 이후 첫번째 .then 핸들러가 호출됩니다.
- 2에서 반환한 값은 다음 .then 핸들러에 전달됩니다.
- 이런 과정이 계속 이어집니다.
result가 핸들러 체인을 따라 전달되므로, alert 창엔 1, 2, 4가 순서대로 출력됩니다.
초보자는 프라미스 하나에 .then을 여러 개 추가한 후, 이를 체이닝이라고 착각하는 경우가 있습니다.
하지만 이는 체이닝이 아닙니다.
예시:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
예시의 프라미스는 하나인데 여기에 등록된 핸들러는 여러 개 입니다.
이 핸들러들은 result를 순차적으로 전달하지 않고 독립적으로 처리합니다.
동일한 프라미스에 등록된 .then 모두는 동일한 결과(프라미스의 result)를 받습니다.
따라서 위 예시를 실행하면 얼럿 창엔 전부 1이 출력됩니다.
이런 식으로 한 프라미스에 여러 개의 핸들러를 등록해서 사용하는 경우는 거의 없습니다.
프라미스는 주로 체이닝을 해서 씁니다.
출처 :
'STUDY > JavaScript' 카테고리의 다른 글
[JS] FormData 설명 정리 (0) | 2023.03.14 |
---|---|
[JS] 음악 및 오디오 플레이어 Javascript 라이브러리 (0) | 2023.03.09 |
[JS] 동기비동기 · 콜백 이해하기 (0) | 2023.02.01 |
[JS] 자바스크립트 내장함수 (0) | 2023.01.19 |
[JS] 브라우저 객체 모델 (0) | 2023.01.16 |