JS
[JS][Udemy] 비동기식 JavaScript
winter17
2023. 4. 13. 12:28
► Call Stack
- 스크립트가 함수를 호출하면, 해석기가 콜스텍으로 함수를 추가해서 함수를 불러낸다
- stack : 후입선출 구조
const multiply = (x, y) => x * y;
const square = x => multiply(x, x);
const isRightTriangle = (a, b, c) => (
square(a) + square(b) === square(c)
)
console.log("BEFORE")
isRightTriangle(3, 4, 5)
console.log(isRightTriangle(3, 4, 5)) // true → 25 === 25
console.log("DONEEEE!")
► JavaScript is a single thread + web API
console.log('Sending request to server!')
setTimeout(() => { // 브라우저야 3초 뒤에 출력해줄래?
console.log('I am at the end of the file!')
}, 3000)
console.log('I am at the end of the file!')
// Sending request to server!
// I am at the end of the file!
// (3초 후)I am at the end of the file!
⎣ setTimeout을 web API에 넘겨서 3초 타이머를 실행한 후 설정한 시간이 지나면 콜백을 실행해서 출력한다
► CallbackHell
- 콜백을 사용해야 한다는 건 우리가 해결할 수 없는 것 혹은 적어도 정해지지 않은 시점에 코드를 실행하기 위한 메커니즘을 제공해야 한다는 것
- setTimeout을 한 번만 사용하는 경우에는 간편하지만 5~6개 종속적인 작업이 있다면 코드의 중첩이 많아지도 가독성이 떨어진다. 이때, 비동기Promises가 필요하다
// 콜백 중첩을 이런식으로 하면 코드가 너무 길어짐
setTimeout(() => {
document.body.style.backgroundColor = 'red';
setTimeout(() => {
document.body.style.backgroundColor = 'orange';
setTimeout(() => {
document.body.style.backgroundColor = 'yellow';
setTimeout(() => {
document.body.style.backgroundColor = 'green';
setTimeout(() => {
document.body.style.backgroundColor = 'blue';
}, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000)
// 변경시킬 배경색 함수를 짜고
const delayedColorChange = (newColor, delay, doNext) => {
setTimeout(() => {
document.body.style.backgroundColor = newColor;
doNext && doNext();
}, delay)
}
// 색상과 타임을 중첩해서 콜백 함수 작성
// STILL A LOT OF NESTING!!!
delayedColorChange('red', 1000, () => {
delayedColorChange('orange', 1000, () => {
delayedColorChange('yellow', 1000, () => {
delayedColorChange('green', 1000, () => {
delayedColorChange('blue', 1000, () => {
})
})
})
})
});
// 하나 이상의 콜백이 있는 경우
// 콜백이 실패할 경우 다음 콜백 실행
searchMoviesAPI('amadeus', () => {
saveToMyDB(movies, () => {
//if it works, run this:
}, () => {
//if it doesn't work, run this:
})
}, () => {
//if API is down, or request failed
})
► Promises
- 어떤 연산, 비동기 연산이 최종적으로 완료 혹은 성공했는지 실패했는지 알려주는 객체
// THE CALLBACK VERSION
const fakeRequestCallback = (url, success, failure) => {
const delay = Math.floor(Math.random() * 4500) + 500;
setTimeout(() => {
if (delay > 4000) {
failure('Connection Timeout :(')
} else {
success(`Here is your fake data from ${url}`)
}
}, delay)
}
// 콜백 요청
fakeRequestCallback('books.com/page1',
function (response) {
console.log("IT WORKED!!!!")
console.log(response)
fakeRequestCallback('books.com/page2',
function (response) {
console.log("IT WORKED AGAIN!!!!")
console.log(response)
fakeRequestCallback('books.com/page3',
function (response) {
console.log("IT WORKED AGAIN (3rd req)!!!!")
console.log(response)
},
function (err) {
console.log("ERROR (3rd req)!!!", err)
})
},
function (err) {
console.log("ERROR (2nd req)!!!", err)
})
}, function (err) {
console.log("ERROR!!!", err)
})
// THE PROMISE VERSION
const fakeRequestPromise = (url) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * (4500)) + 500;
setTimeout(() => {
if (delay > 4000) {
reject('Connection Timeout :(')
} else {
resolve(`Here is your fake data from ${url}`)
}
}, delay)
})
}
// 위의 함수를 호출했을 때 아래와 같은 promises 객체가 나타남
// pending : 무언가를 기다림
// rejected : 실패함
// resolved : 해결됨
fakeRequestPromise('yelp.com/api/coffee/page1')
.then(() => { // resolved일 때
console.log("IT WORKED!!!!!! (page1)")
fakeRequestPromise('yelp.com/api/coffee/page2')
.then(() => { // resolved일 때
console.log("IT WORKED!!!!!! (page2)")
fakeRequestPromise('yelp.com/api/coffee/page3')
.then(() => { // resolved일 때
console.log("IT WORKED!!!!!! (page3)")
})
.catch(() => { // rejected일 때
console.log("OH NO, ERROR!!! (page3)")
})
})
.catch(() => { // rejected일 때
console.log("OH NO, ERROR!!! (page2)")
})
})
.catch(() => { // rejected일 때
console.log("OH NO, ERROR!!! (page1)")
})
// THE PROMISE VERSION
const fakeRequestPromise = (url) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * (4500)) + 500;
setTimeout(() => {
if (delay > 4000) {
reject('Connection Timeout :(')
} else {
resolve(`Here is your fake data from ${url}`)
}
}, delay)
})
}
// THE CLEANEST OPTION WITH THEN/CATCH
// RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN!
fakeRequestPromise('yelp.com/api/coffee/page1')
.then((data) => {
console.log("IT WORKED!!!!!! (page1)")
console.log(data) // Here is your fake data from ${url}
return fakeRequestPromise('yelp.com/api/coffee/page2')
})
.then((data) => {
console.log("IT WORKED!!!!!! (page2)")
console.log(data) // Here is your fake data from ${url}
return fakeRequestPromise('yelp.com/api/coffee/page3')
})
.then((data) => {
console.log("IT WORKED!!!!!! (page3)")
console.log(data) // Here is your fake data from ${url}
})
.catch((err) => {
console.log("OH NO, A REQUEST FAILED!!!")
console.log(err) // Connection Timeout :(
})
► Creating promises
// promises version
const fakeRequest = (url) => {
return new Promise((resolve, reject) => { // new promises는 두 개의 인수를 가진다
const rand = Math.random();
setTimeout(() => {
if (rand < 0.7) {
resolve('YOUR FAKE DATA HERE');
}
reject('Request Error!');
}, 1000)
})
}
fakeRequest('/dogs/1')
.then((data) => {
console.log("DONE WITH REQUEST!")
console.log('data is:', data)
})
.catch((err) => {
console.log("OH NO!", err)
})
// callback version : 중첩이 많아져서 가독성이 떨어짐
const delayedColorChange = (newColor, delay, doNext) => {
setTimeout(() => {
document.body.style.backgroundColor = newColor;
doNext && doNext();
}, delay)
}
delayedColorChange('red', 1000, () => {
delayedColorChange('orange', 1000, () => {
delayedColorChange('yellow', 1000, () => {
delayedColorChange('green', 1000, () => {
delayedColorChange('blue', 1000, () => {
delayedColorChange('indigo', 1000, () => {
delayedColorChange('violet', 1000, () => {
})
})
})
})
})
})
});
// promises version
const delayedColorChange = (color, delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
document.body.style.backgroundColor = color;
resolve();
}, delay)
})
}
delayedColorChange('red', 1000)
.then(() => delayedColorChange('orange', 1000))
.then(() => delayedColorChange('yellow', 1000))
.then(() => delayedColorChange('green', 1000))
.then(() => delayedColorChange('blue', 1000))
.then(() => delayedColorChange('indigo', 1000))
.then(() => delayedColorChange('violet', 1000))
► ASYNC Functions
- promises 구문을 더 깔끔하게 만들어준다
- ASYNC : 함수를 비동기 함수로 선언하는 키워드
const sing = async () => { // 함수 선언시 async키워드를 사용하면 자동으로 promise를 반환함
throw "OH NO, PROBLEM!"
return 'LA LA LA LA'
}
sing()
.then(data => {
console.log("PROMISE RESOLVED WITH:", data) // data = 'LA LA LA LA'
})
.catch(err => {
console.log("OH NO, PROMISE REJECTED!")
console.log(err) // OH NO, PROBLEM!
})
const login = async (username, password) => {
if (!username || !password) throw 'Missing Credentials'
if (password === 'corgifeetarecute') return 'WELCOME!'
throw 'Invalid Password'
}
login('todd', 'corgifeetarecute')
.then(msg => {
console.log("LOGGED IN!")
console.log(msg)
})
.catch(err => {
console.log("ERROR!")
console.log(err)
})
► Await Functions
- async처럼 함수 선언시 함수 내부에서만 사용 가능
- await 키워드는 비동기 함수의 실행을 일시 정지시킴
const delayedColorChange = (color, delay) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
document.body.style.backgroundColor = color;
resolve();
}, delay)
})
}
// delayedColorChange('red', 1000)
// .then(() => delayedColorChange('orange', 1000))
// .then(() => delayedColorChange('yellow', 1000))
// .then(() => delayedColorChange('green', 1000))
// .then(() => delayedColorChange('blue', 1000))
// .then(() => delayedColorChange('indigo', 1000))
// .then(() => delayedColorChange('violet', 1000))
async function rainbow() { // await를 사용하면 promise가 결과를 낼 때까지 기다림
await delayedColorChange('red', 1000)
await delayedColorChange('orange', 1000)
await delayedColorChange('yellow', 1000)
await delayedColorChange('green', 1000)
await delayedColorChange('blue', 1000)
await delayedColorChange('indigo', 1000)
await delayedColorChange('violet', 1000)
return "ALL DONE!"
}
// rainbow().then(() => console.log("END OF RAINBOW!")) 위의 코드가 전부 실행되고나면 해당 문구 출력
async function printRainbow() { // 출력될 코드를 함수로 작성해서 바로 위 코드랑 같은 출력 결과를 나타냄
await rainbow(); // rainbow 함수가 전부 실행될 때까지 기다렸다가
console.log("END OF RAINBOW!") // 출력
}
printRainbow(); // printRainbow 호출
► 비동기 함수의 오류 처리하기
const fakeRequest = (url) => {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * (4500)) + 500;
setTimeout(() => {
if (delay > 2000) {
reject('Connection Timeout :(')
} else {
resolve(`Here is your fake data from ${url}`)
}
}, delay)
})
}
// 오류 처리 : try ~ catch
async function makeTwoRequests() {
try { // 오류가 될 코드를 적으면
let data1 = await fakeRequest('/page1');
console.log(data1);
let data2 = await fakeRequest('/page2');
console.log(data2);
} catch (e) { // 어떻게 처리할지 정의, e는 에러를 나타냄
console.log("CAUGHT AN ERROR!")
console.log("error is:", e)
}
}