13. 비동기 프로그래밍
비동기 처리 방식
동기 처리 방식과 비동기 처리 방식
ex. 커피 전문점에서 커피 주문하고 마시기
- 동기 처리 방식
- A라는 사람이 커피를 주문 -> 그 주문을 받아서 커피를 만들고 A에게 넘겨줌
- 뒤에 아무리 많은 손님이 있어도 한번에 하나의 손님만 처리
- 주문을 받고 커피를 만드는 것이 한 과정
-> 대기 줄이 점점 더 길어지고 주문을 처리하는데도 시간이 걸림
- 비동기 처리 방식
- A라는 사람이 커피를 주문 -> 그 주문을 주방으로 넘김
- A에게는 진동벨을 주면서 커피가 완성되면 알려 주겠다고 함
- 대기하고 있던 B의 주문을 받고 진동벨을 건네줌
- 중간에 A의 커피가 완성되면 A에게 알려 준다
싱글 스레드 vs 멀티 스레드
스레드(thred)
- 프로세스에서 작업을 실행하는 단위
- 싱글 스레드: 한번에 하나의 스레드만 처리
- 멀티 스레드: 한번에 여러 스레드를 사용
자바스크립트) 싱글 스레드 사용 = 한번에 하나의 작업만 처리할 수 있음
시간이 오래 걸리는 작업이 앞에서 실행 중 -> 그 작업이 끝날 때까지 무작정 기다려야 함
=> 시간 ↓ 함수 먼저 처리 -> 멀티 스레드처럼 동작하게 함
// ex.
function displayA() {
console.log("A");
}
function displayB() {
setTimeout(() => console.log("B"), 2000);
}
function displayC() {
console.log("C:);
}
displayA();
displayB();
displayC();
=> A, C, B 순서대로 출력 (A, B, C 순서 X)
-> `display()`를 2초 후에 실행하도록 했기 때문
함수 이름을 콜백으로 사용하기
ex. 사용자가 커피 주문하면 3초 후에 커피가 완성되는 프로그램
// 1) 커피 주문 프로그램
function order(coffee) {
console.log(`${coffee} 주문 접수`);
}
// 2) 완성됐다고 알려주는 프로그램
function display(result) {
console.log(`${result} 준비 완료`);
}
function order(coffee, callback) {
console.log(`${coffee} 주문 접수`);
setTimeout(() => {
callback(coffee);
}, 3000);
}
function display(result) {
console.log(`${result} 준비 완료`);
}
order("아메리카노", display);
익명 콜백 사용
ex. 1초마다 A -> B -> C -> D -> STOP! 순으로 표시
fuction displayLetter() {
console.log("A");
setTimeout( () => {
console.log("B");
setTimeout( () => {
console.log("C");
setTimeout( () => {
console.log("D");
setTimeout( () => {
console.log("stop!");
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}
displayLetter();
=> 콜백 지옥: 콜백이 계속 반복되는 상태
소스의 가독성 ↓, 오류가 발생했을 때 디버깅하기 어려움
프로미스(promise)
- 콜백 안에 콜백, 그 안에 또 콜백 -> 콜백 지옥
- 콜백을 사용했을 때 복잡함 피하기 위해, ES6부터 프로미스 등장
처리에 성공했을 때 실행할 함수, 성공하지 못했을 때 실행할 함수 미리 약속
프로미스 사용 -> `Promise` 객체 먼저 만들어야 함
new Promise(resolve, reject)
- 성공했을 때 실행할 함수: `resolve()`
- 실패했을 때 실행할 함수: `reject()`
프로미스) 객체를 생성(제작)하는 부분, 소비하는 부분
프로미스 제작 코드에서 '성공'과 '실패' 확인 후 소비 코드로 알려줌
ex. 피자 주문 흐름
- `likePizza`가 `true` -> 성공했을 때 실행할 함수에 '피자를 주문합니다' 넘김
- `likePizza`가 `false` -> 실패했을 때 실행할 함수에 '피자를 주문하지 않습니다' 넘김
// 프로미스 제작 코드
let likePizza = true;
const pizza = new Promise((resolve, reject) => {
if (likePizza) resolve('피자를 주문합니다');
else reject('피자를 주문하지 않습니다');
});
프로미스를 실행할 때 사용하는 함수
- `then()`: 프로미스에서 성공했다는 결과를 보냈을 때 실행할 소스
- `catch()`: 프로미스에서 실패했다는 결과를 보냈을 때 실행할 소스
- `finally()`: 프로미스의 성공과 실패에 상관없이 실행할 소스
프로미스객체
.then()
.catch()
.finally();
// 프로미스 소비 코드
pizza
.then(
result => console.log(result)
)
.catch(
err = console.log(err)
)
.finally(
() => console.log("완료");
);
프로미스 상태
`resolve()` 함수나 `reject()` 함수를 매개변수로 받아서 실행하는 객체
실행하면서 상태가 바뀜
상태 | 설명 |
pending | 처음 프로미스를 만들면 대기 상태가 됨 |
fulfilled | 처리에 성공하면 이행 상태가 됨 |
rejected | 처리에 성공하지 못하면 거부 상태가 됨 |
- 제작코드: `fulfilled` 상태인지, `reject` 상태인지
-> '피자를 주문합니다' or '피자를 주문하지 않습니다' 라는 결괏값 넘겨줌 - 소비코드: 결괏값을 result or err 같은 변수 이름으로 받아서 사용
커피 주문하고 완료하는 프로미스
- 프로미스 제작 코드
- 프롬프트 창 ~> 사용자에게 원하는 커피 입력하게 함
- 커피 메뉴 입력 -> 화면에 주문이 접수되었다고 표시, 3초 후에 `resolve` 함수에 커피 메뉴 건내줌
- 커피 메뉴 입력 X -> 주문이 없다는 오류 메시지 표시
- 프로미스 소비 코드
- 성공했으면 `display` 함수, 실패하면 `showErr` 함수 실행
- 화면에 내용을 표시하는 `display` 함수, 오류를 표시하는 `showErr` 함수 만듦
const order = new Promise((resolve, reject) => {
lef coffee = prompt("어떤 커피 주문?", "아아");
if (coffee != null & coffee != "") {
document.querySelector(".start").innerText = `${coffee} 주문 접수`;
setTimeout(() => {
resolve(coffee);
}, 3000);
} else {
reject("커피를 주문하지 않았습니다");
}
});
order // 현재 상태는 fulfilled, 결괏값: 프롬프트 창에서 받은 입력 내용
// Promise {<fulfilled>: '아아'}
order.then() // 프로미스 반환
// Promise {<fulfilled>: '아아'}
// 프로미스 소비 코드
function display(result) {
document.querySelector(".end").innerText = `${result} 준비 완료`;
document.querySelector(".end").classList.add("active");
document.qeurySelector(".start").classList.add("done");
}
function showErr(err) {
document.qeurySelector(".start").innerText = err;
}
order
.then(display)
.catch(showErr);
여러 단계 연결해서 프로그램 만들기
프로그램은 여러 단계 연결해서 사용하는 경우 ↑
ex. 서버에서 학생 자료 가져옴 -> 성공? 가져온 자료를 객체로 만듦
-> 성공? 객체에서 필요한 정보 꺼냄 -> 성공? 화면에 표시...
콜백 함수
ex. 피자 만들기: 피자 도우 준비 -> 토핑 올리기 -> 굽기
const step1 = (callback) => {
setTimeout(() => {
console.log("피자 도우 준비");
callback();
}, 2000);
}
const step2 = (callback) => {
setTimeout(() => {
console.log("토핑 완료);
callback();
}, 1000);
}
const step3 = (callback) => {
setTimeout(() => {
console.log("굽기 완료");
callback();
}, 2000);
}
console.log("피자를 주문합니다.");
step1(function() {
step2(function() {
step3(function() {
console.log("피자가 준비되었습니다.");
}
}
});
프로미스 체이닝
프로미스
- `resolve`와 `reject` 사용해서 성공과 실패에 대한 동작 명확하게 구별 O
- 함수에 계속해서 함수 포함 X 콜백 지옥에서 벗어날 수 있는 좋은 방법
ex. A, B, C 각각 실행 시간이 다르지만 A 작업이 끝날 때까지 기다렸다가 B 작업을 하고,
다시 B 작업이 끝날 때까지 기다렸다가 C 작업을 해야 한다면?
=> 프로미스 체이닝: then()을 사용해 여러 개의 프로미스를 연결하는 것
ex.` A.then(B).then(C)` = `A 프로미스` -return-> `.then(B 프로미스)` -return-> `.then(C 프로미스)`
// 프로미스 제작
const pizza = () => {
return new Promise((resolve, reject) => {
resolve("피자를 주문");
});
};
pizza() // 프로미스 반환
// Promise {<fulfilled>: '피자를 주문'}
// 프로미스 소비
const step1 = (message) => {
console.log(messaeg);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('피자 도우 준비');
}, 3000);
});
};
const step2 = (message) => {
console.log(messaeg);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('토핑 완료');
}, 1000);
});
};
const step3 = (message) => {
console.log(messaeg);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('굽기 완료');
}, 2000);
});
};
pizza()
.then((result => step1(result)) // pizza() 성공 -> step1() 실행
.then((result => step2(result)) // step1() 성공 -> step2() 실행
.then((result => step3(result)) // step2() 성공 -> step3() 실행
.then((result => console.log(result)) // step3() 성공 -> "굽기 완료" 표시
.then(() => {
console.log('피자가 준비되었습니다.');
});
프로미스 소비 코드 줄여쓰기
pizza()
.then(result => step(result)
// 줄여쓰기
pizza()
.then(step1)
pizza()
.then(step1)
.then(step2)
.then(step3)
.then(console.log)
.then(() => {
console.log("피자가 준비되었습니다.");
});
fetch API
- 서버에 자료를 요청하거나 자료를 받아올 때 사용하는 API
- XMLHttpRequset 대신
- 프로미스 반환
fetch(위치, 옵션)
- 위치: 자료가 있는 URL이나 파일 이름
- 옵션: GET/ POST 같은 요청 방식 지정 (기본: GET)
async와 await
프로미스: 콜백 지옥이 생기지 않도록 소스를 읽기 쉽게 바꾼 것
프로미스 체이닝은 프로미스 계속 연결해서 사용 -> 콜백 지옥처럼 소스 복잡해질 수 있음
-> 이런 문제 ↓ => `async` 함수와 `await` 예약어 등장
async 함수
함수 안에 있는 명령을 비동기적으로 실행할 수 있음
async function() {...}
await
- 프로미스 체이닝을 좀 더 쉽게 작성
- `async` 함수에서만 사용할 수 있음
// ex
async function whatsYourFavorite() {
}
async function displaySubject(subject) {
return `Hello, ${subject}`;
}
async function init() {
const response = await displaySubject(response);
console.log(result);
}
init();
=> `whatsYourFavorite()` 함수 처리 시간이 얼마 걸리든 기다렸다가 결괏값 -> `response`에 저장
출처
'한국경제신문 with toss bank > FE' 카테고리의 다른 글
[JavaScript] 프로미스(Promise) (0) | 2024.08.16 |
---|---|
[Do it! 모던 자바스크립트 프로그래밍의 정석] 12. HTTP 통신과 JSON (0) | 2024.08.09 |
[Do it! 모던 자바스크립트 프로그래밍의 정석] 11. 배열과 객체 좀 더 깊게 살펴보기 (0) | 2024.08.09 |
[Do it! 모던 자바스크립트 프로그래밍의 정석] 10. 문자열과 배열 (0) | 2024.08.09 |
[Do it! 모던 자바스크립트 프로그래밍의 정석] 07. DOM 활용하기 (0) | 2024.08.09 |