비동기 (asynchronous)
Promise
- 콜백 내에서 어떤 작업(예: 비동기 작업)을 수행하는 경우 모든 것이 순조롭게 작동하면 resolve가 호출되고 그렇지 않으면 reject가 호출된다
var promise = new Promise(function(resolve, reject) {
// do something
if (/* 잘 동작했다면 */) {
resolve("성공");
}
else {
reject(Error("실패"));
}
});
promise.then(function(result) {
console.log(result); // "성공"
}, function(err) {
console.log(err); // Error: "실패"
});
- promise는 다음 중 하나의 상태를 가진다
- 처리됨(fulfilled) - 프라미스 관련 작업이 성공
- 거부됨(rejected) - 프라미스 관련 작업이 실패
- 대기중(pending): 이행 또는 거부되지 않은 초기 상태
- 해결됨(settled) - 처리되거나 거부되었습니다
- promise의 상태에 따른 흐름
async/await
- promise를 반환하는 getJSON 함수가 있다고 할 때, promise는 JSON 객체를 resolve한다
- resolve한 결과로는 resolve된 JSON을 출력하고 “done” 문자열을 반환한다
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
});
makeRequest().then((result) => {
// do something
})
- 위 코드를 async/await로 작성하면 다음과 같다
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest().then((result) => {
// do something
})
- async로 정의된 함수 내에서만 await를 사용할 수 있다
- 모든 async 함수는 암시적으로 promise를 반환하며, promise의 resolve된 값은 함수에서 반환된 값이 된다 (예제에서는 “done” 문자열)
- await getJSON()은 getJSON() promise가 resolve될 때까지 console.log() 호출을 기다린다는 의미이다
더 나은 이유?
간결하고 깔끔하다
- then()을 사용하지 않아서 익명 함수를 만들고 data라는 변수를 선언하지 않아도 된다
- nested function 사용을 피할 수 있다
try/catch로 error handling이 가능하다
// promise
const makeRequest = () => {
try {
getJSON()
.then(result => {
// 여기서 parse가 에러가 난다면
const data = JSON.parse(result)
console.log(data)
})
// promise의 catch()를 불러서 error handling을 해야한다
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
// async/await
const makeRequest = async () => {
try {
// 여기서 parse가 에러가 난다면
const data = JSON.parse(await getJSON())
console.log(data)
// catch block에서 error handling이 가능하다
} catch (err) {
console.log(err)
}
}
조건부
// promise
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
- nesting과 중괄호, return문이 많아 조건에 대한 결과를 예측하기 쉽지않다
// async/await
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
- aync/await구문을 사용하면 조건에 대해서 더욱 읽기 쉬워진다
중간 값
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
- promise1이 호출되고 그 return 값(중간 값)으로 promise2를 호출하고, 그 return 값으로 promise3를 호출한다
- 위 예제의 nesting을 피하려면 Promise.all로 다시 작성할 수 있다
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
- 이 코드를 async와 await로 사용하면 더 간단한 코드를 만들 수 있다
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
에러 스택
- 다음 코드처럼 여러 promise를 체이닝하여 호출하고 있는데 어딘가에서 에러가 발생한다고 가정해보자
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
- promise의 체이닝 중에 에러가 발생한다면 callAPromise에 대해서만 나오기 때문에 에러난 위치에 대해 오해의 소지가 발생할 수 있다
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})
- async/await 구문으로 하게 되면 makeRequest에 대한 에러를 던져주기 때문에 디버깅할 때 더 도움이 된다
디버깅
const makeRequest = () => {
return callAPromise()
.then(() => CallAPromise())
.then(() => CallAPromise())
.then(() => CallAPromise())
.then(() => CallAPromise())
}
- 크롬 개발자 도구에서 디버깅을 할 때에는 동기 코드에 대해서만 디버깅을 할 수가 있어서 then을 따라 갈 수가 없다
const makeRequest = async () => {
await CallAPromise()
await CallAPromise()
await CallAPromise()
await CallAPromise()
}
- 하지만 async/await 구문을 사용하면 정상적인 동기 호출인 것처럼 호출을 기다릴수 있다