callback / Promise / Async Await
in JavaScript
기본사항 : 자바스크립트는 동기적(synchromous)이다. => 호이스팅처리 이후부터는 위에서부터 한줄한줄씩 실행된다.
호이스팅 ?
var
,function declaration
들이 자동적으로 js파일 최상단으로 올라가는 현상
Callback
원래는
console.log('1')
console.log('2')
console.log('3')
// 1
// 2
// 3
순서대로 나와야하는데,
브라우저에 내장되어있는 함수인 setTimeout
을 사용한다면
console.log('1')
setTimeout(function() {
console.log('2')
})
console.log('3')
// 1
// 3
// 2
이렇게 반환할거다. 이게 바로 비동기(asynchronous) 라고 생각하면 된다.
비동기(asynchronous) ? 언제 코드가 실행될지 예측할 수 없는 것
그렇다면 callback함수는 늘 비동기적으로만 실행되는건가? 하면 그건 또 아니다.
콜백함수는 언제 실행될지 모르는 비동기, 그리고 즉각적으로 실행되는 동기로 나눌 수 있다.
callback지옥
콜백함수들이 유용하게 쓰일때도 있지만, 콜백함수들을 엮고 엮고 엮고 엮으면…. 그걸 콜백지옥이라고 한다.
우선 코드 가독성이 너무너무너무 떨어진다.
어디서 어떤 함수를 호출하는지, 어디서 어떤 에러가 나는지 파악하는것도 상당히 어려워짐.
디버깅이 진짜 어려워진다는거…
Promise
비동기를 간단하게 처리할 수 있도록 도와주는 object.
정해진 기능이 다 수행됐다면 성공을, 수행하는 도중 에러가 생겼다면 실패를 return한다.
Promise는 자바스크립트의 object로 비동기를 실행할때 callback대신 유용하게 쓰일 수 있다.
promise는 2가지 포인트만 잡고가면 좋은데 그게 바로 state와 producer, consumer개념이다.
1. state
promise가 수행되는 상태를 나타낸다.
pending
: 시킨 일을 수행중일때
fulfilled
: 에러없이 완료되었을때
rejected
: 에러발생했을때
2. producer vs consumer
- Producer : promise를 생성하는 입장
비동기적으로 실행되는 promise를 만들어보자. promise는 클래스이기때문에 new를 이용해서 오브젝트를 만들 수 있다.
const promise = new Promise((resolve, reject) => {
//네트워크 통신이나 파일을 읽는것은 시간이 걸리는데 이걸 동기로 처리하면 사용자는 완료가 될때까지 빈 화면만 보고 있어야하므로 이럴때는 동기로 처리해주는것이 좋다.
setTimeout(()=>{
//resolve('성공')
rejct(new Error)
},1000)
})
- 새로운 promise가 만들어질때는 부르지 않아도 자동으로 실행된다.
- Consumers : promise를 가져가 사용하는 입장
promise
.then((value) => {
console.log(value);
})
.catch(e){
console.log(e);
}
.finally(()=>{
console.log('finally')
})
async await
async await는 promise를 조금 더 동기적으로 보이게끔 만들어준다.
promise를 .then()
으로 계속해서 묶어나가면(=프로미스 체이닝) 코드가 조금 난잡해질 수 있는데,
(이런 이미 존재하는 기능 위에 조금더 간편하게 사용할 수 있도록 해주는것을 syntatic suger
이라고 부른다.)
이때 async await를 사용함으로서 promise를 조금더 깔끔하게 스타일링할 수 있다.
그렇다고 모든 promise사용을 지양하고 async await를 권장하는것은 아님!
async
함수앞에 async를 쓰면 그 함수를 자동으로 promise로 변환시켜줌.
프로미스도 너무 중첩적으로 쓰면 콜백지옥과 같은 문제가 발생하는데 그때 사용하면 좋다.
await
async 이 붙은곳에서만 사용할 수 있다.
병렬 프로미스
function getApple(){
...
}
function getBanana(){
...
}
async function pickFruits(){
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise();
const banana = await bananaPromise();
return `${apple} + ${banana}`
}
이렇게 프로미스를 병렬적으로 사용할때가 있는데,
사과를 가져오는것과 바나나를 가져오는것 사이에는 아무런 연관이 없으므로 둘이 동시에 일어나도 상관없다.
이때 사용하는게 Promise.all
인데,
function pickAllFruits(){
return Promise.all([ getApple(), getBanana() ])
.then(fruits => fruits.join('+'));
}
pickAllFruits().then(console.log); //🍎 + 🍌
function pickOnlyOne(){
return Promise,race([ getApple(), getBanana() ]);
}
pickOnlyOne().then(console.log); //🍌
Promise.all
은 프로미스 배열을 전달하게 되 모든 프로미스들을 다 받아올때까지 모아준다.
배열안의 프로미스들이 다 받아지면 배열로 다시 받아온다.
race
는 어떤것이든 상관없고 둘중에 먼저 실행되는 프로미스를 반환한다.