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`에 저장
출처
Do it! 모던 자바스크립트 프로그래밍의 정석
www.youtube.com
'💻 > FE' 카테고리의 다른 글
| [JavaScript] 프로미스(Promise) (0) | 2024.08.16 | 
|---|---|
| [Do it! 모던 자바스크립트 프로그래밍의 정석] 12. HTTP 통신과 JSON (0) | 2024.08.09 | 
| [Do it! 모던 자바스크립트 프로그래밍의 정석] 11. 배열과 객체 좀 더 깊게 살펴보기 (1) | 2024.08.09 | 
| [Do it! 모던 자바스크립트 프로그래밍의 정석] 10. 문자열과 배열 (0) | 2024.08.09 | 
| [Do it! 모던 자바스크립트 프로그래밍의 정석] 07. DOM 활용하기 (0) | 2024.08.09 |