천진난만 코딩 스토리
2023.02.23) 항해 18일차 본문
오늘 공부 내용은 상당히 어려웠다.....
동기 비동기의 차이는 이해가 되는데, promise 부분에서 좀 헤맸다.
다행히 영상의 도움을 받아서 감은 잡혔지만, 역시나 직접 사용해봐야 명확히 알게 될 것 같다.
동기 비동기는 깇이 이해하려고 하면 양이 엄청 많아진다고 한다.
그건 추후에 어느정도 이해가 되었을 때 해보기로 한다.
지금 괜히 시도했다간 지금 이해한 내용도 헷갈릴지 몰라...
1) 동기 / 비동기 (태스크큐, 이벤트루프)
동기는 직렬적으로 태스크를 수행하고, 태스크는 순차적으로 실행되며 어떤 작업이 수행 중이면 다음 작업은 대기한다.
비동기는 병렬적으로 태스크를 수행하고, 태스크가 종료되지 않은 상태라 하더라도 대기하지 않고 다음 태스크를 실행한다.
자바스크립트의 대부분의 DOM 이벤트 핸들러와 Timer 함수(setTimeout, setInterval), Ajax 요청은 비동기식 처리 모델로 동작한다.
① 싱글 쓰레드란
자바스크립트는 싱글쓰레드(single-thread) 이다.
싱글 쓰레드는 쓰레드가 하나뿐이라는 의미이며 이말은 곧 하나의 작업(task)만을 처리할 수 있다는 것을 의미한다.
하지만 실제로 동작하는 웹 애플리케이션은 많은 task가 동시에 처리되는 것처럼 느껴진다.
이처럼 자바스크립트의 동시성(Concurrency)을 지원하는 것이 바로 이벤트 루프(Event Loop)이다.
- Call Stack: 자바스크립트에서 수행해야 할 함수들을 순차적으로 스택에 담아 처리
- Web API: 웹 브라우저에서 제공하는 API로 AJAX나 Timeout등의 비동기 작업을 실행
- Task Queue: Web API에서 넘겨받은 Callback함수를 저장
- Event Loop: Call Stack이 비어있다면 Task Queue의 작업을 Call Stack으로 옮김
2) 콜백 지옥 탈출하기
콜백 기반 비동기 처리는 한 개 혹은 두 개의 중첩 호출이 있는 경우는 보기에도 나쁘지 않다.
콜백 지옥은 꼬리에 꼬리를 무는 비동기 동작이 많아지면 깊은 중첩 코드가 만들어지는 패턴을 말한다.
콜백지옥을 방지하기 위해 promise와 asyanc/await을 사용할 수 있다.
3) promise
new Promise에 전달되는 함수는 executor(실행자, 실행 함수) 라고 부른다.
executor는 new Promise가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다.
executor는 자동으로 실행되는데 여기서 원하는 일이 처리되고, 끝나면 executor는 성공 여부에 따라 resolve나 reject를 호출한다.
- resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
- reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
Promise 객체는 executor와 결과나 에러를 받을 소비 함수를 이어주는 역할을 한다.
소비함수는 .then, .catch, .finally 메서드를 사용해 등록된다.
- .then의 첫 번째 인수는 Promise가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받느다.
- .then의 두 번째 인수는 Promise가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받는다.
- 에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)같이 null을 첫 번째 인수로 전달하면 되고,
- .catch를 써도 된다. (.catch는 .then에 null을 전달하는 것과 동일하게 작동한다)
- .finally에선 성공·실패 여부에 상관없이 절차를 마무리하는 ‘보편적’ 동작을 수행한다.
- .finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다.
① Promise 체이닝
promise 체이닝은 result가 .then 핸들러의 체인(사슬)을 통해 전달된다
promise 체이닝이 가능한 이유는 promise.then을 호출하면 promise가 반환되기 때문이다.
반환된 프라미스엔 당연히 .then을 호출할 수 있다.
핸들러가 값을 반환할 때엔 이 값이 promise의 result가 된다.
// Promise 체이닝 예제코드
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // ① 1초 후 최초 프라미스가 이행
}).then(function(result) { // ② 이후 첫번째 .then 핸들러가 호출
alert(result); // 1
return result * 2;
}).then(function(result) { // ③ 2에서 반환한 값은 다음 .then 핸들러에 전달
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
② fetch와 체이닝 함께 사용하기
fetch는 자바스크립트를 사용하면 필요할 때 서버에 네트워크 요청을 보내고, 새로운 정보를 받아오는 일을 한다.
// url에 네트워크 요청을 보내고 프라미스를 반환한다.
fetch('/article/promise-chaining/user.json')
// 원격 서버가 응답하면 .then 아래 코드가 실행된다.
.then(function(response) {
// response.text()는 응답 텍스트 전체가 다운로드되면
// 응답 텍스트를 새로운 이행 프라미스를 만들고, 이를 반환한다.
return response.text();
})
.then(function(text) {
// 원격에서 받아온 파일의 내용
alert(text); // {"name": "Violet-Bora-Lee", "isAdmin": true}
});
4) Promise API
① Promise.all
요소 전체가 Promise인 배열(엄밀히 따지면 이터러블 객체이지만, 주로 배열)을 받고 새로운 Promise를 반환한다.
모든 Promise가 이행될 때까지 기다렸다가 그 결괏값을 담은 배열을 반환한다.
주어진 Promise 중 하나라도 실패하면 Promise.all는 거부되고, 나머지 Promise의 결과는 무시된다.
그렇기에, Promise 결과가 모두 필요할 때같이 ‘모 아니면 도’ 일 때 유용하다.
② Promise.allSettled
모든 프라미스가 처리될 때까지 기다린다.
반환되는 배열은 다음과 같은 요소를 갖는다.
- 응답이 성공할 경우 – {status:"fulfilled", value:result}
- 에러가 발생한 경우 – {status:"rejected", reason:error}
fetch를 사용해 여러 정보를 가져오고 있고, 여러 요청 중 하나가 실패해도 다른 요청 결과는 여전히 필요하다면,
이럴 때 Promise.allSettled를 사용하면 각 프라미스의 상태와 값 또는 에러를 받을 수 있다.
③ Promise.race
Promise.all과 비슷하지만 가장 먼저 처리되는 프라미스의 결과(혹은 에러)를 반환한다.
첫 번째 프라미스가 가장 빨리 처리상태가 되기 때문에 첫 번째 프라미스의 결과가 result 값이 된다.
이렇게 Promise.race를 사용하면 결과가 나타난 순간 다른 프라미스의 결과 또는 에러는 무시된다.
5) promisification (프로미스화)
콜백을 받는 함수를 프라미스를 반환하는 함수로 바꾸는 것이다.
프로미스화는 async/await와 함께 사용하면 더 좋지만, 콜백을 완전히 대체하지는 못한다.
왜냐하면, Promise는 하나의 결과만 가질 수 있지만, 콜백은 여러 번 호출할 수 있기 때문입니다.
그렇기에, 프로미스화는 콜백을 단 한 번 호출하는 함수에만 적용해야 한다.
프로미스화한 함수의 콜백을 여러 번 호출해도, 두 번째부터는 무시된다.
6) async / await
async가 붙은 함수는 반드시 프로미스를 반환하고, 프로미스가 아닌 것은 프로미스로 감싸 반환한다.
await는 async 함수 안에서만 동작하는데, 프로미스가 처리될 때까지 기다렸다가 결과를 반환한다.
await는 promise.then보다 좀 더 세련되게 프로미스의 result 값을 얻을 수 있도록 해준다.
promise.then보다 가독성 좋고 쓰기도 쉽다.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
let result = await promise; // 프로미스가 이행될 때까지 기다린다.
alert(result); // "완료!"
}
f();
'TIL(Today I Learned)' 카테고리의 다른 글
2023.02.25) 항해 20일차 (1) | 2023.02.25 |
---|---|
2023.02.24) 항해 19일차 (0) | 2023.02.24 |
2023.02.22) 항해 17일차 (0) | 2023.02.22 |
2023.02.21) 항해 16일차 (0) | 2023.02.21 |
2023.02.20) 항해 15일차 (0) | 2023.02.20 |