1011 TIL (콜백함수)

29621 단어 jsjs

잘 모르는 것도 천천히 하면 할 수 있다! 아자!
참조: 드림코딩 by 엘리 자바스크립트 강좌 유튜브

콜백함수

자바스크립트는 동기적인 아이입니다. 바로 호이스팅이 된 이후부터 코드가 우리가 작성한 순서에 맞춰서 하나하나씩 동기적으로 실행된다는 말입니다. 자, 그럼 여기서 호이스팅이 뭐냐면 var 변수와 또는 function 선언 들이 자동적으로 제일 위로 올라가는 것입니다.
그래서 호이스팅이 된 이후부터 코드가 나타나는 순서대로 자동적으로 실행이 된다는 말입니다.

예제를 보면 우리가 console.log (1) (2) (3); 을 작성하게 되면 1,2,3이라고 출력되는걸 볼 수 있습니다.

그래서 동기는 정해진 순서에 맞게 코드가 실행되는 것이고,
비동기는 무엇이냐면 언제 코드가 실행될지 예측할 수 없는 것을 말합니다. 좋은 예제로는 setTimeout()이라는 웹 API가 있는데요. 이것은 브라우저에서 제공되어지는 API로 우리가 지정한 시간이 지나면 우리가 전달한 함수(콜백함수)를 호출하는 것입니다.

콜백함수라는 것은 "우리가 전달해준 함수를 나중에 너가 불러줘." 라는 개념입니다.

setTimeout() 함수에는 핸들러 타임핸들러라는 콜백함수를 전달해주고 어느정도 시간에 Timeout할껀지 시간을 지정해주는 인자들이 있습니다.

console.log('1');
setTimeout(function(){
  console.log('2');
}, 1000);
console.log('3');

우리가 지정한 시간은 1000이 1초입니다. 1초가 지나면 우리가 전달한 함수가 실행되도록 하는 겁니다. cosole.log('2')를 실행되도록할거에요. 그런데 이게 어떻게 된걸까요. 위에서부터 코드가 내려왔는데 1,2,3 순서가 아닌 1,3-(2초 뒤)-2 이렇게 진행되는 것을 볼 수 있습니다.

일단 console.log('1')을 만났으니 1을 먼저 출력합니다.
그 다음 setTimeout이네? 얘는 브라우저 API니까 "브라우저야, 너에게 요청이 하나 왔어. 1초 뒤에 전달해준 콜백을 실행해줘"라고 요청을 해줘요. 이런 브라우저 API는 무조건 브라우저한테 먼저 요청을 보내게 됩니다.
그 다음은 응답을 기다리지 않고 바로 콘솔로그 console.log('3')로 넘어가게 됩니다.
그래서 3을 출력하게 되구요. 브라우저에서 1초에 시간이 지난다음에 다시 "1초 지났어 이 콜백함수 출력해." 그때서야 console.log('2')를 출력하게 됩니다.

이것이 비동기 적인 실행방법입니다.

콜백 마지막 정리

여기에서 우리가 전달하는 이 함수는 바로 실행되는 것이 아니라 setTimeout이라는 함수 안에 하나의 파라미터 인자로 우리가 지정한, 우리가 만든 함수를 전달해줍니다. 그래서 지금 당장 실행하지는 않고 네가 나중에 1초가 지난 다음에 내 함수를 실행해줘.
내 함수는 다시 불러줘. 라고 해서 Callback 함수라고 하는 것입니다.
그리고 이제 보통은 이런 것들은 화살표 함수로 함수라는 것을 선언하지 않고 간단하게 전달할 수 있습니다.

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

자 여기까지 동기와 비동기에 대해서 알아보았는데요.

그럼 콜백은 항상 비동기에서만 쓸까요? 아니요.
콜백도 두 가지의 경우로 나눠줍니다.
즉각적으로 동기적으로 실행되는 동기적 콜백과
나중에 언제실행될지 알 수 없는 비동기적 콜백 으로 나눠져있습니다.

한 번 콜백을 파라미터 인자로 받아서 일을 처리하는 함수를 만들어보겠습니다.

function printImmediately(print) {
  print();
}
printImmediately()

printImmediately 만들어서 뭔진 모르겠지만 print라는 콜백을 받아서 그 콜백을 바로 실행하는 이런 함수를 만들어 볼 수 있습니다. 이 함수를 이용할 때는 printImmediately 를 호출할 건데요. 바로 print라는 콜백함수를 전달 받죠? 그리고 자바스크립트는 타입이 아니라서 어떤 타입에 콜백함수를 받는지는 예측할 수 없지만 아무런 인자가 전달되지 않고 간단하게 콘솔로그를 출력하는 형식을 전달해보도록 하겠습니다.

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

// 동기적 콜백
function printImmediately(print) {
  print();
}
printImmediately(()=> console.log('hello'));

hello가 바로 출력이 되고 우리가 앞에 작성했던 2가 1초 뒤에 출력되는 걸 볼 수 있습니다. 자바스크립트 엔진이 어떻게 했을까요?
자, 함수에 선언은 호이스팅이 된다고 했으니까.
이해하기 쉽게 코드를 움직여보겠습니다.

function printImmediately(print) {
  print();
}

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

// 동기적 콜백
printImmediately(()=> console.log('hello'));

함수의 선언을 제일 위로 올려놓고. 그리고 아래 코드들이 순서대로 선언이 되었고 1을 출력하고 브라우저에 요청하고 3을 호출하고 바로 맨 아래 함수를 호출해서 이 함수도 바로 print하니까
1, 3, hello 그 다음에 2가 출력이 되는 것입니다.

비동기 콜백은 어떻게 구현할 수 있을까요.

function printImmediately(print) {
  print();
}

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

// 동기적 콜백
printImmediately(()=> console.log('hello'));

//비동기 콜백
function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}
printWithDelay(()=> console.log('async callback'), 2000);

printWithDelay 라고 해서 print와 얼마정도 timeout을 하고 싶은지 인자를 두 개 받아오도록 하겠습니다. 그래서 우리는 브라우저 API를 이용해서 우리가 원하는 print라는 콜백함수를 호출하고 timeout이라는 인자를 전달해서 이 함수는 결국 setTimeout을 감싸고 있는 함수인데요. 전달받은 print와 timeout을 settimeout에 요청하는겁니다.
자 우리가 이것을 호출하고 싶다면 printWithDelay(()=> console.log('async callback'), 2000); 이렇게 출력하게 되면

1 (바로 출력)
3 (바로 출력)
hello (print함수를 바로 출력함)
2 (1초 뒤)
async callback (2초 뒤)

이렇게 전달되는걸 볼 수 있습니다.
자바스크립트 엔진이 어떻게 이해했을까요

function printImmediately(print) {
  print();
}

function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}
console.log('1'); // 동기
setTimeout(() => console.log('2'), 1000); // 비동기
console.log('3'); // 동기
printImmediately(()=> console.log('hello')); // 동기
printWithDelay(()=> console.log('async callback'), 2000); // 비동기

콜백 지옥 체험

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
  id,
  password,
  user => {
    userStorage.getRoles(
      user,
      userWithRole => {
        alert(
          `hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      error => {
        console.log(error);
      }
    );
  },
  error => {
    console.log(error);
  }
);

콜백 지옥의 문제점. 가독성이 떨어진다.

이것을 수정해봅시다.

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === 'kimel' && password === 'kimhr08') ||
          (id === 'khr3207' && password === 'kimhr461011')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === 'kimel') {
          resolve({name: 'kimel', role: 'admin'});
        } else {
          reject(new Error('no access'));
        }
      }, 1000);
    });
  }
}


const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then(user => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log);

※ 나를 위한 내용 정리
시간이 걸리는 작업(network, read files)는 Promise로 비동기적으로 처리하는 것이 좋다.
Promise를 만드는 순간 그 안에 전달한 executor 함수는 자동적으로 바로 실행된다.
Promise에는 resolve와 reject라는 콜백함수를 받는 executor라는 콜백함수를 전달헤주어야 한다

Consumers: then, catch, finally
.then() - 성공했을 때
.catch() - 실패했을 때
.finally() - 성공하든 실패하든 상관없이 호출된다.

.then()은 값을 바로 전달할 수도 있고, Promise룰 전달해도 된다.
콜백함수를 전달할 때 받아오는 값으로 바로 함수를 호출하는 경우에는 생략해서 적을 수 있다.
ex)
.then(meal => console.log(meal)) 를
.then(console.log) 와 같이 적을 수 있다.

좋은 웹페이지 즐겨찾기