동기와 비동기
동기
-> 여러개의 작업을 순서대로, 하나씩 표현하는 방식
여러 개의 작업이 있을 때 각 작업을 순서대로 처리하는 것을 동기적으로 처리한다라고 표현한다.
즉, Task A, Task B, Task C가 있다고 가정한다면, Task A가 종료되면 Task B가 실행되고, Task B가 종료되면 Task C가 실행되고 최종적으로는 순서대로 완료하는 흐름으로 표현할 수 있다.

프로그래밍에서 이렇게 작업을 실행하고 처리해주는 단위를 쓰레드라고 부른다.
동기적인 방식의 단점
but, 동기적인 방식에는 치명적인 단점이 존재한다.
-> Task가 오래 걸리는 작업이라면 작업을 처리하기 전 까지는 다음 Task를 실행할 수 없게되어 전체 프로그램의 성능이 악화되어 버리는 치명적인 단점이 존재한다.

이러한 단점을 해결하기 위해, Java나 C# 같은 경우에는 여러개의 쓰레드를 동시에 사용하는 멀티 쓰레드 방법을 활용한다.

여러개의 쓰레드를 사용하면 오래 걸리는 작업이 중간에 포함되어 있다고 하더라도 전체 프로그램의 성능을 악화시키는 데에는 큰 영향을 주지 못하기 때문에 동기적인 방식의 문제점을 어느정도 보완할 수 있다.
but, 아쉽게도 JavaScript 엔진에는 쓰레드가 1개 밖에 없기 때문에 멀티 쓰레드 방식으로는 해결할 수 없으므로, 비동기 방식으로 해결해야 한다.
비동기
-> 작업을 순서대로 처리하지 않는 방식

비동기 방식에서는 여러개의 작업이 주어졌을 때 앞의 작업이 종료되지 않아도, 기다릴 필요 없이 다른 작업을 동시에 실행 시키는 것이 가능하다.

그래서 앞선 작업의 결과 값을 받아서 사용하고 싶다면 각각의 작업에 Callback을 붙여서 사용할 수 있다.
JavaScript는 비동기 작업들을 어떻게 동시에 처리할 수 있는가?
비동기 작업들은 쓰레드에서 실행되는 것이 아닌, 자바스크립트 엔진이 아닌 Web APIs 에서 실행된다.
이러한 비동기 작업들은 아래와 같은 절차로 수행된다.
1. JavaScript Engine은 비동기 함수를 만나게 되면, 이 비동기 함수를 Web Browser의 Web APIs에게 실행해달라고 부탁한다.
2. 이때, 해당 비동기 함수가 끝나면 실행할 콜백 함수까지 넘겨준다.
3. 비동기 함수가 끝나면 콜백 함수를 JavaScript Engine에게 돌려준다.

비동기 처리 방법
1. 콜백 함수
-> 비동기 처리 작업의 결과로 콜백 함수를 달아줄 수는 있지만 아래의 orderFood() 함수처럼 콜백 지옥이 생길 수 있다.
// 콜백 함수
function add(a, b) {
setTimeout(() => {
const sum = a + b; // 3
callback(sum);
}, 3000);
}
add(1, 2, (value) => {
console.log(value); // 3 출력
});
// 음식을 주문하는 상황
function orderFood(callback) {
setTimeout(() => {
const food = "떡볶이";
callback(food);
}, 3000);
}
function cooldownFood(food, callback) {
setTimeout(() => {
const cooldowndedFood = `식은 ${food}`;
callback(cooldownFood);
}, 2000);
}
function freezFood(food) {
setTimeout(() => {
const freezedFood = `냉동된 ${food}`;
callback(freezedFood);
}, 1500);
}
orderFood((food) => {
console.log(food);
cooldowndedFood(food, (cooldownFood) => {
console.log(cooldownFood);
freezFood(cooldownFood, (freezedFood) => {
console.log(freezedFood);
});
});
});
// 3초 뒤 떡볶이 출력
// 2초 뒤 식은 떡볶이 출력
// 1.5초 뒤 냉동된 식은 떡볶이 출력
2. Promise
-> 비동기 작업을 효율적으로 처리할 수 있도록 도와주는 자바스크립트 내장 객체
-> Promise는 setTimeout 함수와 같은 비동기 작업들을 래핑하는 객체
-> 감싸고 있는 비동기 작업을 실행, 관리, 결과 저장, 병렬 실행, 다시 실행 등의 비동기 작업들을 처리하는데에 필요한 모든 기능을 제공해주는 객체
Promise의 3가지 상태

1. 대기(Pending) -> 비동기 작업이 진행중인, 아직 작업이 완료되지 않은 상태
2. 성공(Fulfilled) -> 비동기 작업이 성공적으로 마무리 된 상태
3. 실패(Rejected) -> 비동기 작업이 실패한 상태

해결(resolve) -> 비동기 작업이 성공해서 대기 -> 성공으로 바뀜
거부(reject) -> 비동기 작업이 어떠한 이유로 대기 -> 실패로 바뀜
// 비동기 작업 처리하기 Promise
// resolve -> Promise가 관리하는 비동기 작업을 성공 상태로 바꾸는 함수가 들어있음.
// reject -> Promise가 관리하는 비동기 작업을 실패 상태로 바꾸는 함수가 들어있음.
const promise = new Promise((resolve, reject) => {
// 비동기 작업
// executor
setTimeout(() => {
console.log("안녕");
}, 2000);
});
console.log(promise);
promise의 PromiseState가 pending 상태

// 비동기 작업 처리하기 Promise
// resolve -> Promise가 관리하는 비동기 작업을 성공 상태로 바꾸는 함수가 들어있음.
// reject -> Promise가 관리하는 비동기 작업을 실패 상태로 바꾸는 함수가 들어있음.
const promise = new Promise((resolve, reject) => {
// 비동기 작업
// executor
setTimeout(() => {
console.log("안녕");
resolve();
}, 2000);
});
console.log(promise);
resolve() 추가 후, 2초 뒤 promise의 PromiseState가 pending -> resolved 상태

// 비동기 작업 처리하기 Promise
// resolve -> Promise가 관리하는 비동기 작업을 성공 상태로 바꾸는 함수가 들어있음.
// reject -> Promise가 관리하는 비동기 작업을 실패 상태로 바꾸는 함수가 들어있음.
const promise = new Promise((resolve, reject) => {
// 비동기 작업
// executor
setTimeout(() => {
console.log("안녕");
resolve("안녕");
}, 2000);
});
console.log(promise);
이때, PromiseResult가 undefined로 결과값이 저장되어있지 않은 것을 확인할 수 있는데, resolve() 함수 안에 값을 넣어주면 된다.

// 비동기 작업 처리하기 Promise
// resolve -> Promise가 관리하는 비동기 작업을 성공 상태로 바꾸는 함수가 들어있음.
// reject -> Promise가 관리하는 비동기 작업을 실패 상태로 바꾸는 함수가 들어있음.
const promise = new Promise((resolve, reject) => {
// 비동기 작업
// executor
setTimeout(() => {
console.log("안녕");
// resolve("안녕");
reject("왜 실패했는지 이유....");
}, 2000);
});
console.log(promise);
반면, reject()를 사용하면 콘솔에 아래와 같이 PromiseState와 PromiseReulst가 출력된다.

then 메소드
-> promise가 성공하면, then 메소드에 전달한 callback 함수를 실행시킴.
// then 메소드
// -> promise가 성공하면, then 메소드에 전달한 callback 함수를 실행시킴.
promise.then((value) => {
console.log(value);
});
catch 메소드
-> promise가 실패하면, catch 메소드에 전달한 callback 함수를 실행시킴.
// catch 메소드
// -> promise가 실패하면, catch 메소드에 전달한 callback 함수를 실행시킴.
promise.catch((error) => {
console.log(value);
});
Promise Chaining
-> promise의 then과 catch는 promise 자기 자신을 반환하므로 연결해서 사용하는 것도 가능하다.
// promise의 then과 catch는 promise 자기 자신을 반환하므로 연결해서 사용하는 것도 가능함. (promise chaining)
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log("error");
});
3. Async & Await
Async
-> 어떤 함수를 비동기 함수로 만들어지는 키워드
-> 즉, 함수가 Promise를 반환하도록 변환해주는 키워드
// async
// 어떤 함수를 비동기 함수로 만들어주는 키워드
// 함수가 프로미스를 반환하도록 변환해주는 키워드
async function getData() {
return {
name: "서승환",
id: "seosh817",
};
}
console.log(getData());

Await
-> async 함수 내부에서만 사용이 가능한 키워드
-> 비동기 함수가 다 처리되기를 기다리고 결과를 반환함.
async function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "서승환",
id: "seosh817",
});
});
});
}
// await
// async 함수 내부에서만 사용이 가능한 키워드
// 비동기 함수가 다 처리되기를 기다리고 결과를 반환함.
async function printData() {
const data = await getData();
console.log(data);
// getData().then((result) => {
// console.log(result);
// });
}
printData();

References