지금까지 유데미 섹션에서 가장 어려웠고, 한편으로는 그동안 자바스크립트를 공부하면서 이해하기 힘들거나 애매하게 넘어갔던 부분을 제대로 해소했던 섹션이었다. 복습도 좀 제대로 해주고자 한다.
🌱CALL STACK
우리가 작성한 코드가 여러개의 function call을 요구할 때, 자바스크립트는 call stack 위에 함수를 올려놓고 스택 처리 방법(FIFO)으로 주어진 function call을 진행한다. 호출된 함수를 스택 제일 위에다 두고, 그 함수를 처리한 뒤, 방금 처리한 함수를 스택 위에서 삭제하는 방식이다.
다음 코드는 삼각형의 세 변의 길이가 주어졌을 때 그 삼각형이 직각삼각형임의 여부를 알려주는 함수(isRightTriangle)를 콜하는 과정이다.
const multiply = (x, y) => x * y;
const square = x => multiply(x, x);
const isRightTriangle = (a, b, c) => (
square(a) + square(b) === square(c)
)
isRightTriangle(3, 4, 5)
자바스크립트가 isRightTriangle(3,4,5)단에 도착하여 위 코드를 실행하는 과정에서 call stack을 채워넣는 과정은 다음과 같다.
- isRightTriangle(3,4,5)를 스택의 맨 위에다 둔다.
- square(3)이 콜된다. square(3)을 스택의 맨 위에다 둔다.
- multiply(3,3)이 콜된다. multiply(3,3)을 스택의 맨 위에다 둔다.
- multiply(3,3)의 콜이 완료되고 9라는 결과값이 return된다. 스택의 맨 위에 있던 multiply(3,3)을 제거한다.
- square(3)의 결과값이 return되었다. 스택의 맨 위에 있던 square(3)을 제거한다.
- square(4)가 콜된다. square(4)를 스택의 맨 위에다 둔다. (이하 과정은 동일)
- ...<중간생략>
- isRightTriangle(3,4,5)의 결과값이 true로 return되었다. 스택의 맨 위에 있던 isRightTriangle(3,4,5)를 제거한다.
이렇게 함수가 call 될 때마다 call stack의 맨 위로 함수를 올려 그 함수를 처리하는 구조를 가지고 있는 것이다. 이것이 call stack이다.
🌱 SINGLE THREAD
자바스크립트는 기본적으로 single thread 위에서 동작한다. 자바스크립트가 실행할 수 있는 코드 명령어는 한 줄 씩 뿐이라는 말이다. 그렇기 때문에 자바스크립트의 방식으로 서버와 통신할 때(물론 꼭 자바스크립트 방식이 아니더라도) 딜레이로 인해 혹은 예기치 못한 오류로 인해 에러가 발생하거나 응답 시간이 길어지는 것은 피하지 못할 사고일 것이다.
또한 자바스크립트는 싱글 스레드이므로 한 번에 하나의 코드만 동작시킬 수 있다고 하는데, setTimeout()같은 function을 쓸 때엔 어떻게 하는 것일까?
console.log("Sending request to server!")
setTimeout(() => {
console.log("Here is your data from the server...")
}, 3000)
console.log("I AM AT THE END OF THE FILE!")
// 결과:
// Sending request to server!
// I AM AT THE END OF THE FILE!
// Here is your data from the server...
한 번에 하나의 일만 할 수 있는 우리의 무능한(?) 자바스크립트가, setTimeout()으로 3초를 기다리는 동안 동시에 마지막 console.log 함수를 실행시키는게 가능할까? 사실 setTimeout을 통해 3초를 기다리는 것은 자바스크립트가 아니라, 브라우저(사파리, 웨일, 크롬, 파이어폭스 등..)가 하는 일이다. 자바스크립트는 setTimeout()과 같이 브라우저가 인식할 수 있는 web API를 호출한다. 호출하고 나면? 자바스크립트의 일은 끝이다. 그냥 브라우저한테 web API 함수를 띡 던져주고 너 일해~ 말만 한다. 그리고 또 콜스택의 맨 위에 있는 함수를 처리하느라 바쁘다. 브라우저는 자바스크립트한테 명령받은 API를 잘 수행한 뒤(위의 예시에선 3초를 기다린다), API에 명시된 일(위 예시에선 console.log)을 call back function의 형태로 다시 자바스크립트의 콜스택 위에 올려놓는다. 그러면 모든 것이 부드럽게 돌아간다!
🌱 CALLBACK HELL
setTimeout(callback, sec)은 주어진 sec만큼 기다린 뒤 callback function을 수행하는 대표적인 web API이다. 이걸 이용하면 우리가 원하는 일을 연쇄적으로 실행할 수 있게 된다. 하지만.. 아주 더럽게 그 일을 수행시킬 수도 있다. 만약 우리 홈페이지의 backgroundColor을 바꾸는 일을 하고 싶다고 생각해보자. 지금까지 배운 것으로만 코드를 짜면 다음과 같이 더러운 코드가 완성된다.
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);
첫번째 setTimeout()으로 빨간색이 되고, 그 안의 call back 안에 또 setTimeout()으로 배경이 빨강으로 바뀐 뒤 1초가 지나고 또 주황으로 바뀌고... 이런 연쇄작용이 끊임없이 일어나는 것이다. 코드 자체는 직관적이지만 더럽다.
예전엔 주어진 일을 연쇄적으로 실행시키기 위해 이런 nesting call back을 많이 사용했다고 한다. 첫번째 응답이 제대로 도착해야 두번째를 요구하고, 또 그 두번째 응답이 제대로 도착해야 세번째를 요구하는 방식의 무언가를 짤 때 nesting call back을 이용하는 경우가 잦았다는 것이다. depended action이라는 표현을 사용했는데 맞는 말인 것 같다.
+) api function은 대개 function(param, success_callback, fail_callback)의 형식을 띈다. function이 성공하면 success_callback을 수행하고, 실패하면 fail_callback을 실행하는 것이다. 아래 수도코드가 그 예시인데 이건 promise를 배운 뒤 조금 더 유용하게 쓰인다.
searchMoviesAPI('amadeus', () => {
saveToMyDB(movies, () => {
//if it works, run this:
}, () => {
//if it doesn't work, run this:
})
}, () => {
//if API is down, or request failed
})
🌱 PROMISE
물론 지금은 저렇게 crazy nesting callback을 마구잡이로 사용하진 않는다. 물론 사용할 때도 가끔 있겠지만 우리한텐 "promise"가 있기 때문이다!
promise란? 우리의 asynchronous function이 잘 실행되었는지 혹은 실패했는지를 나타내는 object라고 보면 된다. 명심해라, 오브젝트다! 근데 이제 callback을 attach한. 이걸 하면 이걸 하게 해줄게(resolve), 하지만 이걸 하지 않으면 넌 이걸 해야할거야(reject). 라는 의미를 담고 있어서 promise라고 명명했다고 이해하면 된다. 잘 실행되면 resolve에 attach된 콜백을, 실패하면 reject에 attach된 콜백을 실행할 것이다.
promise의 위대함을 알기 위해 promise를 사용하지 않은 버전과 사용한 버전의 비교를 해보겠다.
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);
};
randomly generated된 delay에 따라 failure 혹은 success가 실행되는 fake request이다. 이 request function을 써서 1페이지가 로딩되면 2페이지를, 2페이지가 로딩되면 3페이지를 연쇄적으로 보여주는 프로그램을 짰다고 생각해보자. 굉장히 더럽다!
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);
}
);
3페이지까지의 데이터를 req했으므로 success, failure function이 각각 3개씩 총 6개가 필요하고, 연쇄적으로 3페이지의 로딩이 이뤄졌으므로 3중 nesting callback이 이뤄진 것을 확인할 수 있다. 읽기 굉장히 불편하고 중괄호 괄호가 어지럽게 붙어있어 썩 보기좋지 않다.
첫번째 request function을 promise 버전으로 나타내보자. promise는 object라고 했다. promise를 이용할 때 함수가 promise object를 return하는 방식을 쓰는 것이다. promise를 쓴 함수의 생성 방식은 다음과 같다.
const reqWithPromise = (param) => {
return new Promise((resolve,reject) => {
if (success) {
resolve(param);
} else {
reject(param);
}
}
}
몇 번이고 강조하지만, resolve는 success_function, reject는 fail_function이다.
fakeRequestCallback을 promise를 써서 바꿔보았다.
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);
});
};
그냥 success와 failure만 바뀐 것 같은데? 뭐가 다르냐? 한다면 then과 catch를 쓸 수 있다는 점이다!
fakeRequestPromise("yelp.com/api/coffee/page1")
.then((data) => {
console.log("IT WORKED!!!!!! (page1)");
console.log(data);
// return next promise -> 그 return된 promise로 또 다음 .then(resolve될때 callback을 담음)을 부른다
return fakeRequestPromise("yelp.com/api/coffee/page2");
})
.then((data) => {
console.log("IT WORKED!!!!!! (page2)");
console.log(data);
return fakeRequestPromise("yelp.com/api/coffee/page3");
})
.then((data) => {
console.log("IT WORKED!!!!!! (page3)");
console.log(data);
})
.catch((err) => {
// 마지막 catch 하나로 reject(err)로 온 err data를 handling.
console.log("OH NO, A REQUEST FAILED!!!");
console.log(err);
});
연쇄적으로 .then을 호출하고 return promise를 하면서 crazy nesting call back을 벗어난 것을 확인할 수 있다. 우리가 로딩하려는 "yelp.com/api/coffee/page#"의 세 페이지중 어느 페이지에서 로딩이 실패하더라도 마지막 .catch에서 걸린다.
구조가 보이나? promise 오브젝트가 resolve되었을 경우 .then의 콜백이 실행되고(콜백 함수의 parameter은 resolve()에 들어있는 parameter) reject되었을 경우 .catch의 콜백이 실행되는 것을 확인할 수 있다!
promise를 이용해서 우리가 최초에 했던, 홈페이지의 background color을 바꾸는 것을 조금 더 깔끔하게 해보자.
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));
reject할 것이 딱히 없으니 .catch블락은 따로 만들어주지 않았다. 훨씬 보기 좋지 않나?!
🌱 ASYNC & AWAIT
마지막으로, async를 조금 더 보기좋고 깔끔하게 만들어주는 newer syntax를 배워보자. async와 await라는 키워드이다.
async는 기억하자. promise를 return하는 함수 앞에 붙인다!!!! 그래서 딱히 return new Promise어쩌고를 하지 않아도 앞에 async 키워드가 붙은 함수가 return "hello"를 하면 resolve("hello")로, throw "hello"를 하면 reject("hello")로 자동 번역이 된다.
function이 return하면 resolved with that value, function이 throw하면 rejected with that value인 것이다.
async function hello() {
return 'hey!';
}
hello();
// Promise {<resolved>: "hey!"}
const uhOh = async () => {
throw 'oh no';
}
uhOh();
// Promise {<rejected>: "oh no"}
await는 async function 안에서만 사용할 수 있는 키워드다. await는 promise가 resolved 되기까지 기다리라는 뜻의 키워드이다! 말이 어려우니 바로 예시를 들어보자면, 웹의 배경색을 바꾸는 프로그램에서 .then을 여러개 쓰는 것보다 아래처럼 나타내는 것이 가독성 측면에서 훨씬 보기 좋다.
async function rainbow() {
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!";
}
각 delayedColorChange() 앞에 await가 붙었으므로 delayedColorchange가 resoved될 때까지 기다리게 된다. 그러면 딱히 연쇄적인 .then을 통해 resolve 후의 콜백을 설정하지 않더라도 자동으로 resolve된 뒤 다음 줄을 실행하게 될테니까 조금 더 간편하다.
엥? 그럼 .then과 .catch가 없으면 await에서 에러 핸들링은 어떻게 하나요! 라고 물어본다면 전통적인 try-catch블락을 사용하면 된다.
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);
});
};
async function makeTwoRequests() {
try {
// 우리가 맨날 할거다.. data가 올때까지 await.
let data1 = await fakeRequest("/page1");
console.log(data1);
let data2 = await fakeRequest("/page2");
console.log(data2);
} catch (e) {
// data1, data2를 뽑는 과정에서 무언가 rejected가 일어난다면 try-catch block을 통해 핸들링한다.
console.log("CAUGHT AN ERROR!");
console.log("error is:", e);
}
}
하.. 1시간 깔끔하게 불태웠다.
지금까지 유데미 섹션에서 가장 어려웠고, 한편으로는 그동안 자바스크립트를 공부하면서 이해하기 힘들거나 애매하게 넘어갔던 부분을 제대로 해소했던 섹션이었다. 복습도 좀 제대로 해주고자 한다.
🌱CALL STACK
우리가 작성한 코드가 여러개의 function call을 요구할 때, 자바스크립트는 call stack 위에 함수를 올려놓고 스택 처리 방법(FIFO)으로 주어진 function call을 진행한다. 호출된 함수를 스택 제일 위에다 두고, 그 함수를 처리한 뒤, 방금 처리한 함수를 스택 위에서 삭제하는 방식이다.
다음 코드는 삼각형의 세 변의 길이가 주어졌을 때 그 삼각형이 직각삼각형임의 여부를 알려주는 함수(isRightTriangle)를 콜하는 과정이다.
const multiply = (x, y) => x * y;
const square = x => multiply(x, x);
const isRightTriangle = (a, b, c) => (
square(a) + square(b) === square(c)
)
isRightTriangle(3, 4, 5)
자바스크립트가 isRightTriangle(3,4,5)단에 도착하여 위 코드를 실행하는 과정에서 call stack을 채워넣는 과정은 다음과 같다.
- isRightTriangle(3,4,5)를 스택의 맨 위에다 둔다.
- square(3)이 콜된다. square(3)을 스택의 맨 위에다 둔다.
- multiply(3,3)이 콜된다. multiply(3,3)을 스택의 맨 위에다 둔다.
- multiply(3,3)의 콜이 완료되고 9라는 결과값이 return된다. 스택의 맨 위에 있던 multiply(3,3)을 제거한다.
- square(3)의 결과값이 return되었다. 스택의 맨 위에 있던 square(3)을 제거한다.
- square(4)가 콜된다. square(4)를 스택의 맨 위에다 둔다. (이하 과정은 동일)
- ...<중간생략>
- isRightTriangle(3,4,5)의 결과값이 true로 return되었다. 스택의 맨 위에 있던 isRightTriangle(3,4,5)를 제거한다.
이렇게 함수가 call 될 때마다 call stack의 맨 위로 함수를 올려 그 함수를 처리하는 구조를 가지고 있는 것이다. 이것이 call stack이다.
🌱 SINGLE THREAD
자바스크립트는 기본적으로 single thread 위에서 동작한다. 자바스크립트가 실행할 수 있는 코드 명령어는 한 줄 씩 뿐이라는 말이다. 그렇기 때문에 자바스크립트의 방식으로 서버와 통신할 때(물론 꼭 자바스크립트 방식이 아니더라도) 딜레이로 인해 혹은 예기치 못한 오류로 인해 에러가 발생하거나 응답 시간이 길어지는 것은 피하지 못할 사고일 것이다.
또한 자바스크립트는 싱글 스레드이므로 한 번에 하나의 코드만 동작시킬 수 있다고 하는데, setTimeout()같은 function을 쓸 때엔 어떻게 하는 것일까?
console.log("Sending request to server!")
setTimeout(() => {
console.log("Here is your data from the server...")
}, 3000)
console.log("I AM AT THE END OF THE FILE!")
// 결과:
// Sending request to server!
// I AM AT THE END OF THE FILE!
// Here is your data from the server...
한 번에 하나의 일만 할 수 있는 우리의 무능한(?) 자바스크립트가, setTimeout()으로 3초를 기다리는 동안 동시에 마지막 console.log 함수를 실행시키는게 가능할까? 사실 setTimeout을 통해 3초를 기다리는 것은 자바스크립트가 아니라, 브라우저(사파리, 웨일, 크롬, 파이어폭스 등..)가 하는 일이다. 자바스크립트는 setTimeout()과 같이 브라우저가 인식할 수 있는 web API를 호출한다. 호출하고 나면? 자바스크립트의 일은 끝이다. 그냥 브라우저한테 web API 함수를 띡 던져주고 너 일해~ 말만 한다. 그리고 또 콜스택의 맨 위에 있는 함수를 처리하느라 바쁘다. 브라우저는 자바스크립트한테 명령받은 API를 잘 수행한 뒤(위의 예시에선 3초를 기다린다), API에 명시된 일(위 예시에선 console.log)을 call back function의 형태로 다시 자바스크립트의 콜스택 위에 올려놓는다. 그러면 모든 것이 부드럽게 돌아간다!
🌱 CALLBACK HELL
setTimeout(callback, sec)은 주어진 sec만큼 기다린 뒤 callback function을 수행하는 대표적인 web API이다. 이걸 이용하면 우리가 원하는 일을 연쇄적으로 실행할 수 있게 된다. 하지만.. 아주 더럽게 그 일을 수행시킬 수도 있다. 만약 우리 홈페이지의 backgroundColor을 바꾸는 일을 하고 싶다고 생각해보자. 지금까지 배운 것으로만 코드를 짜면 다음과 같이 더러운 코드가 완성된다.
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);
첫번째 setTimeout()으로 빨간색이 되고, 그 안의 call back 안에 또 setTimeout()으로 배경이 빨강으로 바뀐 뒤 1초가 지나고 또 주황으로 바뀌고... 이런 연쇄작용이 끊임없이 일어나는 것이다. 코드 자체는 직관적이지만 더럽다.
예전엔 주어진 일을 연쇄적으로 실행시키기 위해 이런 nesting call back을 많이 사용했다고 한다. 첫번째 응답이 제대로 도착해야 두번째를 요구하고, 또 그 두번째 응답이 제대로 도착해야 세번째를 요구하는 방식의 무언가를 짤 때 nesting call back을 이용하는 경우가 잦았다는 것이다. depended action이라는 표현을 사용했는데 맞는 말인 것 같다.
+) api function은 대개 function(param, success_callback, fail_callback)의 형식을 띈다. function이 성공하면 success_callback을 수행하고, 실패하면 fail_callback을 실행하는 것이다. 아래 수도코드가 그 예시인데 이건 promise를 배운 뒤 조금 더 유용하게 쓰인다.
searchMoviesAPI('amadeus', () => {
saveToMyDB(movies, () => {
//if it works, run this:
}, () => {
//if it doesn't work, run this:
})
}, () => {
//if API is down, or request failed
})
🌱 PROMISE
물론 지금은 저렇게 crazy nesting callback을 마구잡이로 사용하진 않는다. 물론 사용할 때도 가끔 있겠지만 우리한텐 "promise"가 있기 때문이다!
promise란? 우리의 asynchronous function이 잘 실행되었는지 혹은 실패했는지를 나타내는 object라고 보면 된다. 명심해라, 오브젝트다! 근데 이제 callback을 attach한. 이걸 하면 이걸 하게 해줄게(resolve), 하지만 이걸 하지 않으면 넌 이걸 해야할거야(reject). 라는 의미를 담고 있어서 promise라고 명명했다고 이해하면 된다. 잘 실행되면 resolve에 attach된 콜백을, 실패하면 reject에 attach된 콜백을 실행할 것이다.
promise의 위대함을 알기 위해 promise를 사용하지 않은 버전과 사용한 버전의 비교를 해보겠다.
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);
};
randomly generated된 delay에 따라 failure 혹은 success가 실행되는 fake request이다. 이 request function을 써서 1페이지가 로딩되면 2페이지를, 2페이지가 로딩되면 3페이지를 연쇄적으로 보여주는 프로그램을 짰다고 생각해보자. 굉장히 더럽다!
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);
}
);
3페이지까지의 데이터를 req했으므로 success, failure function이 각각 3개씩 총 6개가 필요하고, 연쇄적으로 3페이지의 로딩이 이뤄졌으므로 3중 nesting callback이 이뤄진 것을 확인할 수 있다. 읽기 굉장히 불편하고 중괄호 괄호가 어지럽게 붙어있어 썩 보기좋지 않다.
첫번째 request function을 promise 버전으로 나타내보자. promise는 object라고 했다. promise를 이용할 때 함수가 promise object를 return하는 방식을 쓰는 것이다. promise를 쓴 함수의 생성 방식은 다음과 같다.
const reqWithPromise = (param) => {
return new Promise((resolve,reject) => {
if (success) {
resolve(param);
} else {
reject(param);
}
}
}
몇 번이고 강조하지만, resolve는 success_function, reject는 fail_function이다.
fakeRequestCallback을 promise를 써서 바꿔보았다.
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);
});
};
그냥 success와 failure만 바뀐 것 같은데? 뭐가 다르냐? 한다면 then과 catch를 쓸 수 있다는 점이다!
fakeRequestPromise("yelp.com/api/coffee/page1")
.then((data) => {
console.log("IT WORKED!!!!!! (page1)");
console.log(data);
// return next promise -> 그 return된 promise로 또 다음 .then(resolve될때 callback을 담음)을 부른다
return fakeRequestPromise("yelp.com/api/coffee/page2");
})
.then((data) => {
console.log("IT WORKED!!!!!! (page2)");
console.log(data);
return fakeRequestPromise("yelp.com/api/coffee/page3");
})
.then((data) => {
console.log("IT WORKED!!!!!! (page3)");
console.log(data);
})
.catch((err) => {
// 마지막 catch 하나로 reject(err)로 온 err data를 handling.
console.log("OH NO, A REQUEST FAILED!!!");
console.log(err);
});
연쇄적으로 .then을 호출하고 return promise를 하면서 crazy nesting call back을 벗어난 것을 확인할 수 있다. 우리가 로딩하려는 "yelp.com/api/coffee/page#"의 세 페이지중 어느 페이지에서 로딩이 실패하더라도 마지막 .catch에서 걸린다.
구조가 보이나? promise 오브젝트가 resolve되었을 경우 .then의 콜백이 실행되고(콜백 함수의 parameter은 resolve()에 들어있는 parameter) reject되었을 경우 .catch의 콜백이 실행되는 것을 확인할 수 있다!
promise를 이용해서 우리가 최초에 했던, 홈페이지의 background color을 바꾸는 것을 조금 더 깔끔하게 해보자.
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));
reject할 것이 딱히 없으니 .catch블락은 따로 만들어주지 않았다. 훨씬 보기 좋지 않나?!
🌱 ASYNC & AWAIT
마지막으로, async를 조금 더 보기좋고 깔끔하게 만들어주는 newer syntax를 배워보자. async와 await라는 키워드이다.
async는 기억하자. promise를 return하는 함수 앞에 붙인다!!!! 그래서 딱히 return new Promise어쩌고를 하지 않아도 앞에 async 키워드가 붙은 함수가 return "hello"를 하면 resolve("hello")로, throw "hello"를 하면 reject("hello")로 자동 번역이 된다.
function이 return하면 resolved with that value, function이 throw하면 rejected with that value인 것이다.
async function hello() {
return 'hey!';
}
hello();
// Promise {<resolved>: "hey!"}
const uhOh = async () => {
throw 'oh no';
}
uhOh();
// Promise {<rejected>: "oh no"}
await는 async function 안에서만 사용할 수 있는 키워드다. await는 promise가 resolved 되기까지 기다리라는 뜻의 키워드이다! 말이 어려우니 바로 예시를 들어보자면, 웹의 배경색을 바꾸는 프로그램에서 .then을 여러개 쓰는 것보다 아래처럼 나타내는 것이 가독성 측면에서 훨씬 보기 좋다.
async function rainbow() {
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!";
}
각 delayedColorChange() 앞에 await가 붙었으므로 delayedColorchange가 resoved될 때까지 기다리게 된다. 그러면 딱히 연쇄적인 .then을 통해 resolve 후의 콜백을 설정하지 않더라도 자동으로 resolve된 뒤 다음 줄을 실행하게 될테니까 조금 더 간편하다.
엥? 그럼 .then과 .catch가 없으면 await에서 에러 핸들링은 어떻게 하나요! 라고 물어본다면 전통적인 try-catch블락을 사용하면 된다.
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);
});
};
async function makeTwoRequests() {
try {
// 우리가 맨날 할거다.. data가 올때까지 await.
let data1 = await fakeRequest("/page1");
console.log(data1);
let data2 = await fakeRequest("/page2");
console.log(data2);
} catch (e) {
// data1, data2를 뽑는 과정에서 무언가 rejected가 일어난다면 try-catch block을 통해 핸들링한다.
console.log("CAUGHT AN ERROR!");
console.log("error is:", e);
}
}
하.. 1시간 깔끔하게 불태웠다.