Google Javascript 인터뷰 (Callbacks, Promises, Await/Async)
Google Javascript Interview Process에 대해서 정리한 글입니다.
이 글은 GP Lee 님의 Google Javascript Technical Interview 를 번역한 글입니다. Google 인터뷰 내용과 과정에 대해 상세하게 적은 글을 한국 분 들을 위해 번역해 보았습니다. 도움이 되었으면 좋겠네요 :)
원작자 님의 글에 대해 더 관심이 많으신 분들은 여기를 통해 구독해주세요!
이글을 아래의 3가지를 설명한 글입니다.
1. Callbacks
- Javascript 에서 Callback 이 무엇인가요?
- Callback 사용 예제
2. Callbacks vs. ES6 Promises & Await / Async
- 왜 Promises 를 쓰나요?
- Promises 는 무엇인가요?
- Promises 사용 예제
3. Sample Problems
Q1. 영어 알파벳 A, B,C 를 순서대로 Callback, Promise, Async/Await 으로 출력하시오.
Q2. 배열 [A, B, C] 을 순서대로 출력하시오.
Q3. 배열 [A … Z] 을 순서대로 출력하시오.
Q4. Callback API Request 를 정해진 숫자 만큼 보내세요.
그럼 이제 첫번째 목차부터 시작해보겠습니다.
1. Callbacks
Javascript 에서 Callback 이란 무엇인가?
- Javascript 는 웹브라우저를 통해 HTML & CSS 와 함께 웹페이지를 제작할 수 있도록합니다.
- 웹 브라우저에는 수 많은 이벤트들이 일어납니다. 여기서 Javascript의 역할은 이러한 이벤트에 반응하여 효과가 일어나게 하는 것입니다. 이벤트의 예시로는 클릭이나 타이핑이 있습니다.
- Callback을 하는 가장 근본적인 이유는 이벤트에 반응하여 코드가 작동될 수 있게 하기 위해서 입니다. 이벤트가 작동 될때 callback 함수를 부르기 위해서는 callback 와 이벤트를 binding 시켜주는 또 다른 함수가 필요합니다.
실제로 함수를 사용하면서 callback 을 많이 이용합니다. 예를 들어, 아래의 예시에서는 클릭했을때 Hello를 출력하는 웹페이지 입니다.
간단한 Callback 예시
const body = document.getElementsByTagName('body')[0];
function callback() {
console.log('Hello');
} body.addEventListener('click', callback);
설명
- 이 경우에는, 우리는 callback 이라는 함수를 다른 함수 addEventListener 로 보내고 있습니다.
- addEventListener를 부를때, callback 은 ‘click’ event와 함께 호출됩니다.
여기서 javascript는 2가지 방법으로 작동됩니다
- 웹 페이지가 로딩 될때 script 는 한번 돕니다.
- const body 가 불러지고, value가 주어지게 됩니다.
- callback 함수가 선언되지만, 실행되지는 않습니다.
- 함수 addEventListener가 실행됩니다.
- addEventListener의 역할은 ‘click’ event 가 발생할시, callback 함수를 부르는 것입니다.
2. 이벤트가 일어날때 javascript는 작동합니다.
- 이때 callback 함수가 실행됩니다.
- callback 함수가 실행되면, ‘Hello’ 가 console 창에 찍히게 됩니다.
- 이것은 click 이 발생할 때 마다 일 어날 수 있습니다.
이제 callback 어떻게 실생활에 사용되는지 설명해보고자 합니다.
Callback 의 적용예시
아래는 script 와 module을 동시에 로딩하는 loadScript이라는 함수 입니다.
function loadScript(src) { // <script> tag를 만들고 페이지에 추가해 줍니다.
// this causes the script with given src to start loading and run when complete let script = document.createElement('script');
script.src = src;
document.head.append(script);}
- 이 함수의 document에 script 를 추가 해줍니다.
// script를 로딩하고 호출합니다.pathloadScript('/my/script.js');
문제점
- script 는 ‘asynchronous’ 합니다. 즉, 함수들이 동시에 실행 되고 종료 되는 것이 아니라, 여러함수를 한번에 실행시킬 수 있고, 하나의 함수가 종료 되지 않아도 다른 함수가 실행되기 시작할 수 있다는 것입니다.
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
// the code below loadScript
// doesn't wait for the script loading to finish newFunction(); // no such function!
해결책
- loadScript의 두번째 변수로 callback 함수를 불러보겠습니다. script가 로딩 되면서 이 함수는 호출됩니다.
// define function loadScriptfunction loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);} // call function
loadScript('/my/script.js', function() { // script 가 로딩되어야지만 작동
newFunction(); ...
});
- 이렇게 두번째 변수는 callback 함수로 action이 완료되어야지만 작동 되는 함수 입니다.
Callback in Callback
ES6 Promise & Async/Await 의 예시로 넘어가기 전, callback 예제를 하나더 집고 넘어가겠습니다.
- 어떻게 두 개의 script를 순차적으로 로딩할까요>
- loacScript 함수안에 callback 함수를 넣어주는 것입니다.
loadScript('/my/script.js', function(script) { loadScript('/my/script2.js', function(script) { loadScript('/my/script3.js', function(script) {
// ...continue after all scripts are loaded
});})});
우리는 이제 callback함수를 어떻게 쓰는지에 대한 이해를 했습니다. 이제 ES6 Promise로 넘어가겠습니다.
2. Callback vs. ES6 Promise
왜 Promise 을 쓰나요?
- callback 함수가 중첩 될 수록 코드는 더 관리하기 어려워집니다. 여기서 코드의 중첩이란, 특정 코드가 종료 될때까지 기다리지 않고 바로 다음코드를 실행한다는 문제입니다. 이것을 해결하기 위해서 Promise 를 사용합니다.
Promise 은 무엇인가요?
- Promise은 ES6가 callback 문제를 해결하기 위해 소개했습니다.
Promise를 이용한 loadScript 함수
// Promise 방식으로 함수를 정의하는 것function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src; script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script);
});
} // 쓰임let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
); promise.then(script => alert('Another handler...'));
- Promise 는 순차적으로 함수를 실행시키는 것을 가능하게 합니다. loadScript 가 먼저 실행되고, then 이 실행됩니다.
- Promise에 then을 원하는 횟수만큼 부를 수 있습니다.
Promises 사용예제
3. 문제 예시
(Q1 — *Callback) 영어 글자 A,B,C 를 순서대로 출력하시오.
먼저, 함수 printString을 만들어서 console에 string을 출력하고, 변수를 가진 callback 함수를 setTimeout() 함수를 가지고 시간차를 둡니다.
function printString(string, callback) {
setTimeout(
() => {
console.log(string)
callback()
},
1 * 1000 // 1s
)
}
A, B, C를 순서대로 출력합시다.
function printAll() {
printString("A", () => {
printString("B", () => {
printString("C", () => {})
})
})
} printAll()
(Q1 — *Promise) 영어 글자 A,B,C 를 순서대로 출력하시오.
function printString(string) {
return new Promise((resolve, reject) => {
setTimeout(
() => {
console.log(string)
resolve()
},
1 * 1000
)
})
}
A, B, C를 순서대로 출력합시다.
Promise 에서는 then을 이용해서 다음 함수로 넘어갑니다. 이경우에는, 다른 printString() 함수를 부릅니다.
function printString(string) {
return new Promise((resolve, reject) => {
setTimeout(
() => {
console.log(string)
resolve()
},
1 * 1000
)
})
}
(Q1 — *Await Async) 영어 글자 A,B,C 를 순서대로 출력하시오.
Await Async 는 Promise에 기반을 둔 언어 입니다.
쓰임
function printAll() {
printString("A")
.then(() => {
return printString("B")
})
.then(() => {
return printString("C")
})
} printAll()
Async Await function
async function printAll() {
await printString("A")
await printString("B")
await printString("C")
} printAll()
(Q2 — *Callback) 배열 [A,B,C]를 출력하시오.
인터뷰에서 문제를 해결했을때, 질문을 항상 살짝 변형 시켜서 물어봅니다.
그래서 이 두번째 질문은 첫번째 질문을 조금 변형 시킨 것 입니다.
그냥 string 형태의 A, B ,C 가 아닌 이것을 배열 형태로 만들었습니다.
Callback Way
function printAll() {
const arr = ["A", "B", "C"]
let index = -1
printString(arr[++index], () => {
printString(arr[++index], () => {
printString(arr[++index], () => {})
})
})
} printAll()
Usage
function printString(string, callback) {
setTimeout(
() => {
console.log(string)
callback()
},
1 * 1000 // 1s
)
}
(Q3) 알파벳 전체를 배열 에 담아 출력하시오.
이문제는 어떻게 해결할까요? 아래는 잘못된 방법 인 callback을 여러번 부르는 방법입니다.
function cbHell() {
XX(param, () => { // 1 callback
XX(param, () => { // 2 callback
XX(param, () => { // 3 callback
... // forever (hell)
})
})
})
} printAll()
이렇게 해결하면 안되죠, 그래서 이 함수를 최적화 시켜 보겠습니다.
이 경우에는 알파벳 26 글자를 26개의 ‘callback hell’ 을 만들면서 부르는 것이 아니라, callback 속에서 callback을 만드는 방법으로 부르도록 하겠습니다.
function
- 먼저 우리는 arr 라는 배열을 정의하고 그속에 모든 알파벳, [“A”, ”B”, “C”, … “Z”] 를 담겠습니다.
- 우리는 index를 정의하겠습니다. 이 index는 printString()을 부를때 마다 1씩 증가하는 형태로 만들겠습니다.
- printString 함수는 3가지 변수를 pass 하도록 하겠습니다 ( an item from arr, index, callback )
- 그리고 함수 cbOfcb 를 부르도록 하겠습니다.
function printAll(){
let arr = [...Array(26)].map((val, i) => String.fromCharCode(i + 65));
// console.log(arr) // ["A", "B", "C", "D" ... "Z"] let index = 0;
setTimeout(function cbOfcb() {
let array = arr
if(index < 27){
// string , index , callback 의 변수를 pass 해줍니다.
printString(array[index++], index, callback);
cbOfcb(); // call itself
} }, 1000)
} printAll()
printString function
- printAll 와 callback 함수 사이의 중간역할을 해줍니다.
- index 가 27 이되면, cb err 를 보내줍니다.
- 이게 아니면, 우리는 cb null & string을 보내줍니다.
callback function
- 만약 err 가 보내지게 되면, done을 출력하고 return합니다.
- 만약이게 아니라면, string(str)을 출력해줍니다.
function callback(err, str) {
if(err) {
console.log('done');
return
}
console.log(str);
}
(Q4) Make Callback API Request for a given number of times
javascript 에서 ‘ascychronous’ 하게 API request를 보내기도 합니다. 하나의 request 보냈을 경우 request가 실패할 가능성이 있기 때문에 오히려 여러번 request를 보내는 것이 더 효과적입니다.
request function
- axios post request
request 가 성공적이라면
- response.data 에 어떤 값이 들어오게 됩니다. 이경우에는 (response.data ==1) 입니다.
If not (else)
- retry가 0 이상이면 다시 request를 보냅니다.
- 더 이상 남은 retries가 없다면, callback 함수를 부릅니다.
// run the request. this function will call itself max. 5 times if the request failsrequest(5, callback); function request(var retries, var callback) {
axios.post("URL").then(
response => {
if(response.data == 1) {
// request successful, return response (Data)
callback(response);
}
else {
// request failed
// retry, if any retries left
if(retires > 0) {
request(--retires, callback);
}
else {
// no retries left, calling callback with error
callback([], "out of retries");
}
}
}) .catch(error => {
// ajax error occurred
if (retries > 0) { request(--retries, callback);
}
else {
// no retries left
callback([], error);
}
});
}
callback function
- Q3 의 callback function 과 비슷하게 parameter의 값을 확인해줍니다. 이 parameter 의 값으로는 error 혹은 data가 return 될 수 있습니다.
if error
- return error
Else
- print data
// your callback gets executed automatically once the data is receivedvar callback = (data, error) => {
// consume data
if (error) {
console.error(error);
return;
}
console.log(data);
};