비동기 처리는 네트워크 통신에서만 해당하는 내용이 아니라 특정 로직에서도 충분히 사용될 수 있습니다. 그렇기 때문에 비동기 처리를 이해하는 것은 매우 중요합니다.
function getMovie(cb){
fetch(API_URL)
.then(res => res.json())
.then(res => {
cb(res)
})
}
getMovie(movies => {
console.log(movies)
})
하지만 콜백함수를 이용한 비동기 처리 방식의 경우 콜백지옥에 빠질 수도 있어, 이런 경우 작성하기도 어려워지지만 해석하기도 관리하기도 어렵다는 매우 큰 단점이 있습니다.
이러한 콜백지옥을 개선하기 위해 나온 것이 바로 Promise 클래스 입니다.
function getMovie() {
//promise instance가 반환되게 됩니다.
return new Promise((resolve) => {
fetch(API_URL)
.then(res => res.json())
.then(res => {
resolve(res)
})
})
}
getMovie().then(res => console.log(res))
자바스크립트에서 기본적으로 내장되어 제공되는 fetch
함수처럼 getMovies라는 함수를 만들어 호출하고 그 결과를 then으로 받아 사용하는 구조를 만들었습니다.
언제 끝날지 알 수 없는 영화정보를 가져오는 로직을 getMovie로 호출한 후 비동기코드 then을 이용해 로직이 끝나면, 그 때 결과를 콜백안에서 사용하겠다는 작동원리입니다.
getMove() 코드 실행 결과 Promise Instance를 반환하기 때문에 .then(res => console.log(res))
대신에 async await를 사용해 표현할 수 있습니다.
즉, getMovie().then(res => console.log(res))
과 const res = await getMovie()
의 코드 실행 결과는 같습니다.
하지만 async
는 await
가 포함된 함수로 감싸줘야하기 때문에 아래와 같이 정리할 수 있습니다.
function getMovie() {
//promise instance가 반환되게 됩니다.
return new Promise((resolve) => {
fetch(API_URL)
.then(res => res.json())
.then(res => {
resolve(res)
})
})
}
;(async function () {
const res= await getMovie()
console.log(res)
})()
특히 비동기코드에서 예외상황이란 언제든지 발생할 수가 있습니다. 따라서 예외처리를 해주어야합니다.
aysnc
, await
을 사용하는 경우에는 try, catch문을 사용해 예외처리를 할 수 있습니다.
async function getMovie() {
try {
let res = await fetch(API_URL)
res = await res.json()
return res
} catch (error) {
alert('에러가 발생했어요')
} finally {
console.log('실행완료')
//ex) loading 애니메니션 종료
}
}
then()
을 사용하는 경우에는 try, catch문을 사용하지 않고 아래와 같이 catch
메소드와 finally
메소드를 통해 작성할 수 있습니다.
function getMovie() {
fetch(API_URL)
.then(res => res.json())
.then(res => res)
.catch(error=> alert('에러발생'))
.finally(() => {console.log(error)})
}
하지만 async, awiat를 사용하는 방식이 조금 더 권장되고 있기 떄문에 실제로는 try,catch문을 쓸 일이 조금 더 많을 수 있습니다.
function getMovie() {
fetch(API_URL) //실행후
.then(res => res.json()) //여기가 아니라
.then(res => {
console.log('fetch1', res)
})
fetch(API_URL) //여기가 실행
.then(res => res.json())
.then(res => {
console.log('fetch2', res)
})
}
getMovie()
위와 같이 요청이 두 번 발생한 경우, API_URL요청이 거의 동시에 출발하기 떄문에 누가 먼저 도착할지는 예측할 수 없습니다.
만약, 발생 순서를 보장해주고 싶은 경우라면 아래와 같이 코드를 작성할 수 있습니다.
function getMovie() {
fetch(API_URL)
.then(res => res.json())
.then(res => {
console.log('fetch1', res)
fetch(API_URL)
.then(res => res.json())
.then(res => {
console.log('fetch2', res)
})
})
}
getMovie()
하지만 위 코드의 경우 콜백지옥과 비슷하다고 할 수 있습니다.
위와 같은 코드를 보완하기 위해 Promise.all
개념을 사용할 수 있습니다. Promise.all
을 이용하면 비동기 통신이 모두 완료될 때까지 기다릴 수 있습니다.
1번 : 한번에 처리(직렬)
function getMovie() {
Promise.all([ //실제로 통신하는 두코드를 배열로 묶습니다.
fetch(API_URL)
.then(res => res.json()),
fetch(API_URL2)
.then(res => res.json())
])
.then( res => {//res는 배열로 객체구조분해로 사용할 수도 있습니다.
console.log('fetch1:', res[0])
console.log('fetch2:', res[1])
console.log('Done!')
})
}
async, awiat
패턴으로 표현하는 경우 아래와 같습니다.
2번 :병렬
async function getMovie() {
const res1= await fetch(API_URL)
.then(res => res.json())
const res2 = await fetch(API_URL2)
.then(res => res.json())
console.log('async1:', res1)
console.log('async2:', res2)
console.log('Done!')
}
getMovie()
하지만 위 코드에는 문제가 있습니다. 바로, 첫번째 데이터가 도착을 해야지만 두번째 요청이 들어간다는 것입니다.
만약 데이터가 오는데 10초가 걸린다고 가정을 할 때 1번 코드는 10초전후의 시간이 걸리지만, 2번 코드의 경우 20초가 넘게 걸리게 되는 것입니다.
마찬가지로 Promise.all을 사용해 아래와 같이 수정하는 경우 마찬가지로 동시에 데이터를 처리할 수 있습니다.
async function getMovie() {
const [res1, res2] = await Promise.all([
fetch(API_URL)
.then(res => res.json()),
fetch(API_URL2)
.then(res => res.json())
])
console.log('async1:', res1)
console.log('async2:', res2)
console.log('Done!')
}
getMovie()