먼저, 동기식과 비동식의 차이를 알아보면,
직렬적으로 테스크(task)를 수행한다. 테스크는 순차적을 실행되며 어떤 작업이 수행 중이면 다음 작업은 대기한다.
서버에서 데이터를 가져와서 화면에 표시하는 작업을 수행할 때, 서버에 데이터를 요청하고 데이터가 응답될 때까지 이후 테스크들은 블로킹(blocking, 작업 중단)된다.

병렬적으로 테스크를 수행한다. 즉, 테스크가 종료되지 않은 상태라도 대기하지 않고 다음 테스크를 실행한다.
서버에서 데이터를 가져와서 화면에 표시하는 태스크를 수행할 때, 서버에 데이터를 요청한 이후 서버로부터 데이터가 응답될 때까지 대기하지 않고(Non-Blocking) 즉시 다음 태스크를 수행한다. 이후 서버로부터 데이터가 응답되면 이벤트가 발생하고 이벤트 핸들러가 데이터를 가지고 수행할 태스크를 계속해 수행한다.

자바스크립트의 대부분의 DOM 이벤트 핸들러와 Timer 함수(setTimeout, setInterval), Ajax 요청은 비동기식 처리 모델로 동작한다.
javascript에서 제공하는 비동기를 간편하게 처리하도록 도와주는 오브젝트이다.
프로미스는 전통적인 콜백 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다는 장점이 있다.
어떤 시점에 결과를 제공해주는 미랫값이다!
callback1(function(value1) {
callback2(value1, function(value2) {
callback3(value2, function(value3) {
callback4(value3, function(value4) {
callback5(value4, function(value5) {
// value5를 사용하는 처리
});
});
});
});
});
비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩(nesting)이 되어 복잡도가 높아지는 현상이 발생한다. 이런 경우를 '콜백지옥'에 빠졌다고 한다.
callback hell은 코드의 가독성을 나쁘게 하고 복잡도를 증가시켜 실수를 유발하는 원인이 되며, 가장 문제점은 에러처리가 곤란하다는 것이다.
try {
setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
console.log('에러를 캐치하지 못한다..');
console.log(e);
}
try 블록 내에서 setTimeout 함수가 실행되면 1초 후에 콜백 함수가 실행되고 이 콜백 함수는 예외를 발생시킨다. 하지만 이 예외는 catch 블록에서 캐치되지 않는다. 이미 try catch 문을 빠져나온 다음에 setTimeout 함수가 1초 뒤에 실행되기 때문이다.
예외(exception)는 호출자(caller) 방향으로 전파된다. 하지만 위에서 살펴본 바와 같이 setTimeout 함수의 콜백 함수를 호출한 것은 setTimeout 함수가 아니다. 따라서 setTimeout 함수의 콜백 함수 내에서 발생시킨 에러는 catch 블록에서 캐치되지 않아 프로세스는 종료된다.
이러한 문제를 극복하기 위해 Promise가 도입되었다.
promise는 비동기 처리가 성공했는지 또는 실패했는지 상태 정보를 갖는다.

Producer (정보제공) vs Consumer (정보소비)
const promise = new Promise((resolve, reject) => {
});
new Promise에 전달되는 함수는 excutor(실행자, 실행함수)라고 한다. 새로운 Promise가 만들어질 때는 excutor 콜백 함수가 바로 실행된다. 이 콜백 함수는 resolve와 reject 함수를 인자로 전달받는다.
프로미스는 비동기 작업을 전달받아서 응답에 따라 두 가지 메서드 중 하나를 호출하는 객체이다. 비동기 처리에 성공하면 resolve 메서드를 호출하고 then() 메소드에 처리 결과를 넘겨준다. 비동기 처리에 실패하면 reject 메서드를 호출하고 catch() 메서드로 에러메시지를 전달한다.
promise의 객체의 후속 처리 메소드(then, catch, finally)를 통해 비동기 처리 결과 또는 에러메시지를 전달받아 처리한다. 상태에 따라 메소드를 체이닝 방식으로 호출한다.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('jjini');
}, 2000);
});
promise
.then((value) => {
console.log(value);
}) // 2초 후 'jjini' 출력
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('jjini');
reject(new Error('no network'));
}, 2000);
});
promise
.catch((error) => {
console.log(error);
}) // 2초 뒤 "Error: no network" 출력
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
// 성공,실패 상관없이 무조건 마지막에 호출됨
.finally(() => {
console.log('finally'); // finally 출력
});
function Apple() {
return "🍎";
}
function Banana() {
return "🍌";
}
function AllFruits() {
return Promise.all([Apple(), Banana()]).then((fruits) => fruits.join(" + "));
}
AllFruits().then(console.log);
// 🍎 + 🍌 출력
.all()은 promise 배열을 전달하게 되면 모든 promise들이 병렬적으로 다 받을때까지 모아준다.
function pickOnlyOne() {
return Promise.race([Apple(), Banana()]);
}
pickOnlyOne().then(console.log); // 🍎 출력
.race()를 사용하면 배열의 전달된 promise 중에서 가장 먼저 값을 리턴하는 것만 전달이 된다.
프로미스를 async/await을 이용해 더 간편하고 깔끔하게 비동기를 작성할 수 있다. 함수 앞에 async 키워드를 써주면된다. 비동기 함수의 내부에서 await 키워드를 사용하면 값이 반환될 때까지 함수의 실행을 중지시킬 수 있다.
function getApple() {
return "🍎";
}
function getBanana() {
return "🍌";
}
async function pickFruits() {
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(console.log);