지난 포스트까지 콜백함수를 통해 비동기를 처리하는 것을 알아보았다. 오늘은 비동기로 작동하는 코드를 제어할 수 있는 방법 중 Promise 를 활용하는 방법에 대해 정리해보겠다 ! 또한 프로미스 사용을 간결하게 하기 위한 Async/Await 에 대해서도 정리해보겠다 !
Promise 를 공부하면서 헷갈리는 부분이 정말 많았다 . 헷갈렸던 부분, 새롭게 알게된 사실과 개념을 중심으로 글을 작성해갈 예정이다.
Promise
Promise 는 클래스이므로 new 키워드를 통해 Promise 객체를 생성할 수 있다 .
인자로는 비동기 처리를 수행할 콜백 함수 ( executor) 를 전달 받는데 이 콜백 함수는 resolve, reject 함수를 인자로 전달받는다.
Promise 객체가 생성된 후 -> executor 자동으로 실행되며 작성했던 코드들이 작동 -> 코드 정상 처리 ( reslove) , 에러 발생 (reject)
let promise = new Promise((resolve, reject) => {
// 1. 정상적으로 처리되는 경우
// resolve의 인자에 값을 전달할 수도 있습니다.
resolve("value");
// 2. 에러가 발생하는 경우
// reject의 인자에 에러메세지를 전달할 수도 있습니다.
reject("error");
});
-> new Promise 후 ( 첫 괄호로 시작되어 세미콜론 전 ) 까지가 executor 이고 인자로 resolve, reject 를 받는 것
따라서 위의 코드를 콘솔로 찍어 결과를 확인해본다면
생성한 promise 는 정상적으로 실행되어 resolve 가 실행되었고 인자로 넘긴 'value' 가 찍히는 것을 확인 할 수 있다.
위의 콘솔을 살펴보면 내부 프로퍼티가 보인다.
Promise 객체 내부 프로퍼티
new Promise가 반환하는 Promise 객체는 state , result 내부 프로퍼티를 갖는다.
하지만 !! 중요한 사실은 직접 접근할 수 없으므로 .then , .catch , .finally 메서드를 사용해야 접근이 가능하다.
State
pending (대기) | 기본 상태 |
fulfilled (이행) | 콜백함수 executor 가 성공적으로 작동 |
rejected (거부) | 에러 발생 |
따라서 처음 기본 상태는 pending 으로 머물러있다 promise 가 불리고 콜백함수가 실행되는 것의 결과에 따라 이행 , 거부로 나뉜다. 위의 예제의 경우에는 성공적으로 코드가 작동했으므로 fulfilled 로 상태가 변경되었다.
Result
초기에 result 는 undefined 이다. 어찌보면 당연한 것이다. 그 후 콜백함수가 성공적으로 작동하여 resolve(value) 가 호출되면 인자인 value로 , 에러가 발생하여 reject (error) 가 호출되면 error 로 변한다.
헷갈렸던 개념중 하나는 resolve 가 실행되면 무조건 value를 결과로 낸다는 것인줄 알고있었는데 즉 어떠한 값을 낸다 ? 이렇게 생각했었는데 그 뜻이 아니라 resolve에 넘겨준 인자가 result 가 된다는 것이었다. 만약 resolve() 처럼 아무것도 인자로 넘겨주지 않는다면 성공적으로 콜백함수가 실행되어도 result 에는 undefined 가 전달된다.
then, catch, finally
then
executor 에 작성했던 코드가 정상적으로 처리가 되면 resolve 함수를 호출하고 .then 메서드로 접근 할 수 있다.
또한 .then 안에서 리턴한 값이 Promise 면 Promise의 내부 프로퍼티 result 를 다음 .then의 콜백함수의 인자로 받아오고 , Promise가 아니라면 리턴한 값을 .then 의 콜백함수 인자로 받아 올 수 있다. 이렇게 promise 를 수행하고 수행하고 하는 것을 Promise chaning 이라고 한다.
.then 메서드를 이용해 resolve 함수가 실행되면서 인자로 넘겨준 result 를 다음 then 의 콜백 함수의 인자로 받아온다고 생각하면 쉽다. 아까 위의 예제에서 'value' 라는 값을 인자로 넘겨주었을 때 다음 .then 으로 접근해 console.log 로 찍어보면 'value' 가 나온다 !
Catch
executor 에 작성했던 코드가 에러가 발생했을 땐 reject 함수를 호출하고 .catch 메서드로 접근 할 수 있다.
사실 Catch 를 사용하는 경우 즉 reject 를 사용하는 경우가 헷갈렸었다. 사용자가 에러라고 판단해서 reject 를 실행하게 하는 것인지 (만약 if 문을 사용했을 때 false 가 나오는 부분을 에러라고 표현하는 것) 아니면 정말 실행을 했을 때 빨간줄로 표시되는 에러를 뜻하는 것인지 긴가민가 했었다. (왜나하면 여태 에러가 뜨면 코드가 실행 되지 않았으므로 .. 진짜 에러가 발생하면 코드가 중지되는 줄 알았다 ... ) 공부해본 결과 에러는 정말 시스템 내에서 발생하는 에러일 때 작동하는 것이었고 사용자가 직접 에러컨트롤을 하게 된다면 정말 유용하게 사용할 수 있을 것이라고 생각했다. 따라서 위에 경우에도 에러 객체를 생성해서 인자로 넣어주어 에러를 발생하게 만들었다고 볼 수 있다.
Finally
executor 에 작성했던 코드들의 정상 처리 여부와 관계 없이 .finally 메서드로 접근 할 수 있다.
여기까지 promise 에 대해 공부해보았는데 그래서 promise 는 어떻게 비동기를 처리하는지 궁금할 것이다.
바로 Promise chaning 을 통해 비동기 작업을 순차적으로 진행 할 수 있다 .
Promise chaining
.then ,.catch ,.finally 메서드에 대한 예제를 위에서 살펴보았는데 결과가 나타난 후 아래에 promise 객체가 반환된 것을 보았을 것이다 . 위의 메서드는 promise에 접근할 수 있고 promise를 리턴하기 때문에 즉 .then 을 통해 promise를 연결할 수 있고 연결을 해줌으로써 비동기의 순서를 보장할 수 있게 되는 것이다.
let promise = new Promise(function(resolve, reject) {
resolve('성공');
reject('실패');
});
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
위의 코드를 콘솔창에서 확인해보자
처음 변수 promise는 새로운 promise 객체를 생성해주었고 resolve의 인자로는 '성공'을 reject의 인자로는 '실패' 를 작성해 콜백함수 (executor )를 생성해주었다.
그 후 처음 promise .then 의 인자인 value 에는 처음 변수 resolve의 결과인 '성공' 에 접근한다 . 따라서 콘솔에는 성공이 찍히고 '성공' 을 반환해주었기 때문에 '성공' 이 다음 .then의 인자로 들어간다. 이렇게 3번의 성공이 찍히고 .catch 를 만났을 때 에러가 업식 때문에 실행이 되지 않고 .finally 는 성공 여부와 관계없이 실행되므로 콘솔에 출력된다. 마지막으로 리턴한 '성공'이 return 값으로 들어간 프로미스 객체를 반환하며 마무리 된다. 만약 .then 의 콜백함수에 return 을 쓰지 않으면 다음 .then 은 undefined 값이므로 return 을 해주어야 한다.
Promise.all()
promise.all() 은 여러개의 비동기 작업을 동시에 처리하고 싶을 때 사용한다. 인자로는 배열을 받고 해당 배열에 있는 모든 promise 에서 executor 내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise 를 반환한다.
만약 1초 , 2초 , 3초가 걸리는 코드가 있다면 그냥 promise 를 사용한다면 총 6초가 걸린다. 하지만 Promise.all을 사용한다면 실행시간은 3초로 줄어든다. 이것만 보면 Promise.all() 이것만 써야지 ! 라고 생각할 수 있지만 만약 인자로 받은 배열 promise 중 하나라도 에러가 발생하면 즉시 실행을 종료한다. 따라서 이 점을 유의해 사용하는 것이 좋다.
const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
근데 promise 를 사용하다 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell 이 발생할 수 있다. 따라서 이를 해결하기 위해 ES8에서 async / await 키워드를 제공했다.
Async/Await
복잡한 Promise 코드를 간결하게 작성할 수 있게 해준다. 함수 앞에 async 키워드를 사용하고 async 함수 내에서만 await 키워드를 사용해 순서를 제어할 수 있다. await 키워드가 작성된 코드가 동작하고 나서 다음 순서의 코드가 동작하기 때문이다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = async () => {
await printString('A');
await printString('B');
await printString('C');
};
printAll();
Async / Await 를 사용하면 기존에 동기적으로 코드를 작성했던 방식 그대로 순차적으로 키워드만 붙여 작성하면 되니 간결한 것 같다 !
오늘은 이렇게 Promise 와 async/await 에 대해 알아보았다.
사실 아직 콜백함수를 제대로 사용하지도 못하는데 비동기로 코드를 작성한다니... 눈앞이 깜깜했지만 이번 비동기에 관해서 페어프로그래밍을 진행하면서 페어분과 서로의 궁금점을 공유하고 서로 대답해주고 얻어가는게 정말 많았던 시간이었다. 내가 무심코 알고 있다고 생각한 부분도 페어님이 질문해주셔 다시한번 개념을 바로 잡을 수 있었고 새롭게 알게 된 사실도 정말 많았다.
페어프로그래밍이라는 활동의 이점을 다 경험한 활동이었다.
'HTML-CSS-JavaScript > JavaScript' 카테고리의 다른 글
JSON에 대해 (0) | 2023.04.12 |
---|---|
[자료구조/알고리즘] javascript로 재귀 풀기 (0) | 2023.04.11 |
[javascript] Callback을 통한 비동기 처리 (0) | 2023.03.21 |
[javascript] 비동기 - 타이머 API (0) | 2023.03.21 |
[javascript] 동기-비동기 용어 설명 (0) | 2023.03.17 |