본문 바로가기
면접,이직

구글 입사 면접 질문 (javascript)

by F.E.D 2021. 3. 30.

구글에 입사하기 위해서 2020년 기준 자바스크립트 질문들에 대해서 소개드립니다.

부제 : Callbacks, Promises, Await/Async

 

1. Callback

간단한 callback 예시

const body = document.getElementsByTagName('body')[0];

function callback() {
  console.log('Hello');
}

body.addEventListener('click', callback);

EventListener 이벤트 함수도 콜백을 사용합니다.

  • 이 경우 "콜백"이라는 함수를 다른 함수 "addEventListener"에 전달합니다.
  • addEventListener가 실행되면 click 이벤트와 함께 콜백을 등록합니다.

여기서 JavaScript를 실행하는 방법에는 두 가지가 있습니다.

 

1. 페이지가 로드 될 때 스크립트가 한 번 실행됩니다.

  1. body가 상수로 선언되고 body 엘리먼트가 할당됩니다.
  2. callback 함수가 선언되지만, 실행되지 않습니다.
  3. addEventListener가 실행됩니다.(콜백은 addEventListener로 전달됩니다.)
  4. body에 클릭 이벤트가 일어나면 addEventListener가 실행해야될 일 중 하나로 브라우저에게 콜백 함수를 실행하라고 합니다.

 

2. JavaScript가 실행되는 다른 시간은 이벤트가 발생할 때입니다

  1. 이번에는 콜백이 마지막으로 실행됩니다.
  2. body를 클릭하면 콜백이 실행되고 콘솔에 "Hello"가 기록됩니다.
  3. 이것은 두 번 이상, 즉 본문을 클릭 할 때마다 발생할 수 있습니다.

 

callback의 실제 예제

스크립트와 모듈을 비동기적으로 로드하는 loadScript라는 함수를 만들어 보겠습니다.

function loadScript(src) {
  // script태그를 만들고 document의 head에 append 합니다.
  let script = document.createElement('script');
  script.src = src;
  document.head.append(script);
}

// 실행
loadScript('/my/script.js');

브라우저는 자동으로 로드를 시작하고 완료되면 실행됩니다.

문제점

loadScript('/my/script.js');
// 아래의 함수는 loadScript를 기다려주지 않습니다.
newFunction(); // 실행되지 않음!

해결

스크립트가 로드 될 때 실행되어야 하는 loadScript에 두 번째 인수로 콜백 함수를 추가해 보겠습니다.

// loadScript를 정의
function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(script);

  document.head.append(script);
}

// 실행
loadScript('/my/script.js', function() {
  // script가 다 로드되면 callback이 실행됩니다.
  newFunction(); // 작동!
});

두 번째 인수는 작업이 완료 될 때 실행되는 함수(일반적으로 익명)입니다.

 

callback 안의 callback

Promise와 Async/Await 예제를 살펴보기 전에 한 가지 예시를 더 보고 넘어가죠.

어떻게 두 개의 스크립트를 순차적으로 로드 할 수 있습니까?

자연스러운 해결책은 두 번째 loadScript 호출을 콜백 안에 넣는 것입니다.

loadScript('/my/script.js', function(script) {
  loadScript('/my/script2.js', function(script) {
    loadScript('/my/script3.js', function(script) {
      // 순차적으로 실행됩니다. 모든 scrip가 로드되고 실행되는 구간
    });
  })
});

 

2. Callback vs.Es6 Promise

운명의 피라미드라는 말과 콜백 지옥이라는 말이 있습니다.

콜백 지옥을 resolve(해결) 하기 위해서 만들어졌습니다.

// promise 방식으로 loadScript를 변경하였습니다.
function loadScript(src) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    script.src = src;

	// script가 로드가 되면 callback()함수를 실행하던 것에서 resolve(script)를 호출하는 것으로 변경
    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");

// 변수에 담아서 그 변수에 담긴 return new Promise 함수가 호출이 완료되면 resolve 또는 reject를 then으로 받습니다.
promise.then(
  script => alert(`${script.src} is loaded!`), // script를 resolve로 보냈으니까 이렇게 받을 수 있습니다.
  error => alert(`Error: ${error.message}`) // reject 일 때는 error를 만들어서 보낸 것을 이렇게 받습니다.
);

promise.then(script => alert('Another handler...'));

 

3. Sample - 1번 문제 A,B,C를 순서대로 찍으세요.

callback

function printString(string, callback) {
  setTimeout(
    () => {
      console.log(string)
      callback()
    },
    1 * 1000 // 1s
   )
}


function printAll() {
  printString("A", () => {
    printString("B", () => {
      printString("C", () => {})
    })
  })
}

printAll()

* 중요 : 4번째 함수를 에러를 방지하기 위해서 빈 함수를 호출해야합니다.

 

Promise

function printString(string) {
  return new Promise((resolve, reject) => {
    setTimeout(
      () => {
        console.log(string)
        resolve()
      },
      1 * 1000
      )
  })
}

function printAll() {
  printString("A")
  .then(() => {
    return printString("B")
  })
  .then(() => {
    return printString("C")
  })
}

printAll()

 

async/await

function printString(string) {
  return new Promise((resolve, reject) => {
    setTimeout(
      () => {
        console.log(string)
        resolve()
      },
      1 * 1000
      )
  })
}

async function printAll() {
  await printString("A")
  await printString("B")
  await printString("C")  
}

printAll()

new Promise로 return되는 함수를 async로 감싸주고 then으로 순서를 이을 필요 없이 await함수로 호출해줍니다.

4. Sample - 2번 문제 알파벳 배열 ["A","B","C"] 를 사용하여 인쇄

여기에서 조금 더 심화하면 A,B,C string 대신 배열인 ["A","B","C"] 를 제공합니다.

function printAll() {
  const arr = ["A", "B", "C"]
  
  let index = -1
  printString(arr[++index], () => {
    printString(arr[++index], () => {
      printString(arr[++index], () => {})
    })
  })
}

printAll()

5. Sample - 3번 문제 알파벳 배열 [A… Z]을 순서대로 인쇄

콜백 지옥 대신에 여러번 콜백하고 싶은 심정입니다.

 

최적화를 해보지요!

  • arr배열에 알파벳들을 담은 뒤에  printString 호출 배열 [index ++] 후 매번 1 씩 증가하는 index를 정의합니다.
  • printString 함수에서 3 개의 매개 변수를 전달합니다 (arr, index, callback의 항목).
  • 가장 중요한 것은 cb0fcb 함수를 호출하는 것입니다
function printString(string, indx, cb){
      if(indx == 27) {
        cb('err')
      }
      cb(null, string)
}

function callback(err, str) {
 
  if(err) {
    console.log('done');
    return
  }
  console.log(str);
}

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){
      printString(array[index++], index, callback); // 전달 매개변수 -  string , index , callback
      cbOfcb(); // if 조건에 맞을 때 까지 재귀 함수를 호출합니다.
    }    
  }, 1000)
}

printAll()

6. Sample - 4번 문제 주어진 횟수만큼 콜백 API 요청하기

// run the request. this function will call itself max. 5 times if the request fails
request(5, callback);

function request(var retries, var callback) {
  axios.post("URL").then(
    response => {
      if(response.data == 1) {
        // request가 성공적으로 호출되면 callback함수를 호출합니다.
        callback(response);
      }
      else {
        // 실패했으면 retries 횟수를 체크하고 없으면 호출하지 않습니다.
        if(retires > 0) {
          request(--retires, callback);
        }
        else {
          // 아무런 retries 횟수가 없으면 callback으로 error를 뱉습니다.
          callback([], "out of retries");
        }
      }
    })
  .catch(error => {
    // ajax 서버 오류 발생! -> 다시 리트라이 횟수만큼 리트라이해봅니다.
    if (retries > 0) {
      request(--retries, callback);
    }
    else {
      // retries가 있는지 체크하고 없으면 그냥 에러입니다.
      callback([], error);
    }
  });
}

 

 

감사합니다.

 

 

 

 

출처 : medium.com/developers-tomorrow/google-javascript-technical-interview-7a20accd6ddf

댓글