동기적으로 코드를 실행시키는 JS 에서 만약 데이터를 받아오는데 10초가 걸리는 코드가 있다면, 이후의 코드들을 작업이 끝날 때까지 기다려야 할 것이다.
function fetchUser(){
//do network request in 10sec...
return "ellie"
}
const user = fetchUser();
//동기적으로 실행되기 때문에 아래의 코드들은
//10초동안 기다려야 한다
//... 여러 코드들...
console.log(user)
만약 이 코드 뒤에 웹페이지의 UI를 표시하는 코드가 있다면 위 작업이 끝날때까지 데이터가 들어오지 않아 빈페이지만 볼 것이다.
따라서 이것을 비동기적으로 처리함에 따라 데이터를 받아와서 보여주는 부분을 제외한 UI들을 먼저 보여줄 수 있도록 하는 비동기 작업이 필요하고, 이때 필요한 것이 Promise 와 async & await 이다.
promise 객체는 네트워크 통신으로 데이터가 준비되는 대로, then함수에서 Result 값을 받을 수 있다
단 resolve 나 reject 콜백함수를 사용해야만 정상적인 promise 객체를 return 할 수 있고, 따라서 promise 객체의 Result 값을 then 함수에서 받을 수 있다.
이때 return 하는 promise 객체 값의 State 는 "fulfilled" 상태이며, Result 는 콜백함수의 인자값이다.
콜백함수(resolve, reject)를 사용한 경우
function fetchUser(){
return new Promise ((resolve, reject) => {
//do network request in 10sec...
resolve('ellie')
})
}//promise 객체를 return
const user = fetchUser();
//데이터를 비동기적으로 받아온다
user.then(console.log)
//데이터가 준비되었을 때 비동기적으로 실행된다
console.log(user)//async.js: 14
만약 그냥 return 문을 사용하면 정상적인 promise 객체를 return 할 수 없고, 따라서 then 함수에서 promsie 객체의 Result 값을 받을 수 없다.
이때 return 하는 promise 객체 값의 State 는 "pending" 상태이며, Result 는 "undefined" 이다.
return 문을 사용한 경우
function fetchUser(){
return new Promise ((resolve, reject) => {
//do network request in 10sec...
return "ellie"
})
}//promise 객체를 return
const user = fetchUser();
user.then(console.log)
console.log(user)//async.js: 14
promise 안에서는 resolve 나 reject 로 꼭 마무리를 해주자
async 를 이용하면 promise 를 이용하지 않고도 간단하게 비동기적으로 사용할 수 있다.
함수 앞에 async 키워드를 붙이면 자동적으로 함수 안에 있는 코드 블럭들이 promise 로 변환되어진다.
async function fetchUser(){
//do network request in 10sec...
return "ellie"
}
const user = fetchUser();
user.then(console.log);
console.log(user);
await 를 async 와 함께 사용한다면 비동기 작업을 동기적인 코드를 쓰는 것처럼 작성이 가능하다. await 은 async 가 붙은 함수 내에서만 사용할 수 있다.
await 는 비동기 작업 내에서 동기적인 작업을 할때 사용되며, promise chanining 과 같은 용도로 사용된다.
예시
function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}//ms 후에 resolve를 호출하는 promise 를 리턴
async function getApple(){
await delay(3000);//await 은 delay 함수가 끝날때까지 기다린다(동기적인 작업)
return "★";//결과적으로 3초후에 return 됨
}
위와 같은 코드가 있다고 할때,
setTimeout 은 비동기 함수이기 때문에 delay(3000) 앞에 await 을 써주지 않으면 3초뒤에 setTimeout 의 callback 함수인 resolve가 실행이 되어도, 비동기적으로 처리되기 때문에 getApple 함수를 호출하면 바로 return 문이 실행되어 "★" 모양이 출력된다.
하지만 await 을 delay(3000) 앞에 붙인다면 비동기 작업이라도 끝날때까지 기다리기 때문에 비동기 작업을 동기적으로 작동하는 것처럼 사용할 수 있다. 따라서 setTimeout 함수가 끝나는 3초 후에 return 문이 실행되어 "★" 모양이 출력된다.
이 작업은 예전에 만들었던 MovieApp 에서 영화 정보를 불러오는데도 사용할 수 있는데 이런 느낌을 사용할 수 있다.
위 코드의 동작 순서는 console.log(data)
-> conosle.log("etc...")
-> data.then(console.log)
이다.
console.log(data)
는 동기적으로 바로 출력이 되지만 아직 result 값을 가져오지 못했기 때문에, promise 객체를 출력하지만 아직 Result 에 return 된 값이 없고, State 는 pending 일 것이다.
그 다음으로 역시 동기적으로 console.log("etc...")
문이 실행되고,
마지막으로 비동기로 작동하는(프로미스객체가 return 할때까지 기다리는) data.then(conosole.log)
문이 작동한다.
////
쨋든 다시 돌아와서 프로미스도 너무 중첩적으로 chaining하면 콜백지옥과 비슷한 문제점이 발생한다(pickFruits 함수).
function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}//ms 후에 resolve를 호출하는 promise 를 리턴
async function getApple(){
await delay(1000);//await 은 delay 함수가 끝날때까지 기다린다(동기적인 작업)
return "★";//결과적으로 3초후에 return 됨
}
async function getBanana(){// async & await 버전(동기적인 코드를 쓰는 것처럼 작성 가능)
await delay(1000);
return "●"
}
/*
function getBanana2(){
return delay(3000)
.then(()=> "●")//promise chaining 해야한다.
}
*/
function pickFruits(){
return getApple().then(apple => {
return getBanana1().then(banana => `${apple} + ${banana}`)
})
}
pickFruits().then(console.log)//★ + ● (2초뒤)
따라서 프로미스 또한 async & await 을 이용하여 간단하게 처리해준다.
=> 동기적으로 코드를 작성하는 것처럼 쓰고 return 값도 자연스럽게 가져올 수 있어서 간편한다. 또한 에러처리도 try & catch 등의 기존의 방법을 사용할 수 있다.
async function pickFruits(){
const apple = await getApple();
const banana = await getBanana();
return `${apple} + ${banana}`
}
pickFruits().then(console.log)//★ + ● (2초뒤)
여기에는 한가지 문제점이 있다.
위 코드는 바나나와 애플을 받아오는데 각각을 기다리기 때문에 총 2초가 걸리게 된다. 하지만 바나나와 애플을 가져오는데에는 서로 연관이 없기 때문에 기다릴 필요가 없다. 따라서 병렬적으로 사용할 수 있다.
async function pickFruits(){
//프로미스를 만들면 만들어지는 즉시 promise안에 있는 코드블럭이 (병령적으로, 동시)실행된다.
const applePromise = getApple();
const bananaPromise = getbanana();
//그 후 await 로 동기화 시켜주면 된다.
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(console.log)//★ + ● (1초뒤)
Promise.all API 를 사용할 수도 있다.
Promise.all API 로 프로미스 배열을 전달하면 프로미스를 다 받을때까지 모아준다. 이후 다 받아진 배열이 전달되고 이 배열을 여러 메서드로 사용하면 된다.
function pickAllFruits(){
return Promise.all([getApple(),getBanana()])
.then(fruits => fruits.join(' + '))
}
pickAllFruits().then(console.log)//★ + ● (1초뒤)