[20220731_callback, promise]

YunTrollpark·2022년 7월 31일
0

callback

1. Javascript is synchronous

: 자바스크립트는 동기적임. hosting된 순간부터 코드가 우리가 작성한 순서대로 하나하나 동기적으로 실행됨.(hoisting은 변수, function declaration 이런 함수 선언들이 자동적으로 제일 위로 올라가는 것)

console.log('1');
console.log('2');
console.log('3');
// 우리가 작성한 '1' → '2' → '3' 순서대로 출력됨

2. asynchronous

: 비동기적으로 언제 코드가 실행될지 예측할 수 없는 것.
ex) setTimeout → 웹 api(브라우저에서 제공되는 api, 우리가 지정한 시간이 지나면 우리가 전달한 callback 함수를 호출)

console.log('1');
setTimeout(() => 
  console.log('2');
, 1000) // 바로 실행되는 것이 아니라 setTimeout이라는 함수 안에 하나의 parameter 인자로 우리가 지정한 만든 함수를 전달해줌
// 그래서 당장 실행하지 않고, 1초가 지난 다음에 내 함수를 call해줘 → callback
console.log('3');
// 결과: '1' →'3' →'2'

자바스크립트 엔진은 위에서부터 아래로 코드를 차근차근 실행 → 일단 '1'을 만나서 출력 → 'setTimeout? 브라우저에게 1초 뒤에 callback을 실행하라고 요청' → 브라우저 api는 일단 먼저 브라우저에 요청을 보냄, 응답을 기다리지 않고 바로 console.log로 넘어감 → '3'출력 → 그 후 브라우저에서 1초의 시간이 지나면 console.log('2')실행

3. callback은 비동기일때만 사용하나?

1) 즉각적으로 동기적으로 사용하는 Synchronous callback

function printImmediately(print){
  print();// print라는 callback함수를 전달받음
}
printImmediately(() => console.log('hello'));
// 결과: hello

2) 언제 실행될지 예측할 수 없는 Asynchronous callback

function printWithDelay(print, timeout){
 setTimeout(print, timeout); // 전달받은 print와 timeout을 setTimeout에 요청
}
printWithDelay(() => console.log('async callback'), 2000);

: 자바스크립트는 함수를 콜백형태를 인자로 다른 함수에 전달할 수 있고, 변수에 할당가능한 언어. 언어들마다 콜백을 지원하는 방식은 다름!

4. callback 지옥

: callback함수들을 계속 묶어서 nesting하면서 callback 함수 안에서 다른 callback 함수를 부르고, 부르고, 부르고! → promise와 async를 사용하여 정리 가능!

// 콜백지옥 예제
function randomTime() {
  return Math.floor(Math.random() * 10) * 1000;
}

function errorFunction() {
  console.log('재고가 없습니다.');
}


function getFruit(callback, errorFunction) {
  if (callback) {
    setTimeout(() => {
      console.log('이마트 -> fruit');
      callback();
    }, randomTime());
  } else {
    errorFunction();
  }
}

function getSnack(callback, errorFunction) {
  if (callback) {
    setTimeout(() => {
      console.log('이마트 -> fruit -> snack');
      callback();
    }, randomTime());
  } else {
    errorFunction();
  }
}

function getBeverage(callback, errorFunction) {
  if (callback) {
    setTimeout(() => {
      console.log('이마트 -> fruit -> snack -> water');
      callback();
    }, randomTime());
  } else {
    errorFunction();
  }
}

// 이렇게 하면 실행순서 예측가능...??? Nope...

getChicken(() => {
  getSnack(() => {
    getBeverage(() => {}, errorFunction);
  }, errorFunction);
}, errorFunction);

// 원하는대로 순서를 정하려면 이런식으로 callback 안에 callback을 넣어서 콜백지옥처럼 코드를 구성해야함
// 출력순서는 동묘시장 -> frurit -> snack -> water 맞췄지만... 이게 정상은 아님!

콜백지옥의 문제점은
1️⃣ 읽기가 거북함, 가독성이 떨어짐
2️⃣ 에러가 발생하거나, 디버깅해야하는 경우 어디서 처리가 되는지 찾기가 어려움(유지보수가 어려움)
콜백지옥에서 벗어나려면 promise 사용해야함!

Promise

Promise is a JavaScript object for asynchronous operation.
: JavaScript에서 기본적으로 비동기를 처리하기 위해 제공하는 Object. 정해진 장시간의 기능을 수행하고 나서 정상적으로 기능이 수행되면 성공의 메세지와 함께 처리된 결과값을 전달해주고, 기능을 수행하다가 예상치 못한 문제 발생시 에러를 전달해줌.

1️⃣ state: process가 무거운 opereation을 수행하고 있는 중인지, 기능 수행이 완료돼서 성공했는지 or 실패했는지 이런 상태에 대해서 이해해야함.
• promise의 상태는 promise가 만들어져서 우리가 지정한 operation이 수행중일 때: pending상태
• operation을 성공적으로 다 끝낸경우: fullfiled 상태
• file을 찾을 수 없거나, network에 문제가 생기면: rejected 상태

2️⃣ producer vs consumer : 우리가 원하는 데이터를 제공하는 사람과(producer), 제공 된 데이터를 소비하는 사람의(consumer) 차이점을 잘 이해해야함

1) Producer

: Promise는 class라서 앞에 new라는 keyword를 사용해서 object생성가능

// when new Promise is created, the executor runs automatically!
const promise = new Promise((resolve, reject) => {
  // doing some heavy work(network, read files) 무거운 작업은 비동기로 처리!
  console.log('doing something...');
  // 결과: 'doing something...' 출력
  // promise를 만드는 순간 우리가 전달한 executor라는 callback함수가 바로 실행됨
  // 만약 여기에 network와 통신하는 함수를 작성했다면 promise가 만들어지는 순간 바로 network 통신을 수행
  setTimeout(() => {
    resolve('ellie'); // 기능을 성공적으로 수행시 resolve라는 callback 함수 호출
  }, 2000);
});
// 해당 promise는 2초 정도 어떤 일을 하다가 결국 이를 잘 마무리해서 resolve라는 callback함수를 호출해서 'elloe'라는 값을 전달

2) Consumer

: then, catch, finally를 이용해서 값을 받아올 수 있음.
1️⃣ then 예시

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ellie');
  }, 2000);
});
// 값이 정상적으로 잘 수행된다면, 그러면(then) 어떤 값(value)을 받아올거임
// 그리고 우리가 원하는 기능을 수행하는 callback 함수를 전달해주면 됨!
// 여기서 value라는 parameter는 promise가 정상적으로 잘 수행돼서, 마지막으로 resolve callback함수에서 전달된 값이 들어옴!
promise.then((value)=>{
  console.log(value);
});
// 결과: 2초 후에 console창에 'ellie가 출력됨'

2️⃣ reject 예시

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('ellie');
    reject(new Error('no Network'));
    //reject는 보통 Error라는 object를 통해서 값을 전달
  }, 2000);
});
promise
  .then((value)=>{
  console.log(value);
  // 결과: Uncaught (in promise) Error: no Network
})
.catch(error => {
  console.log(error);
});
// Error handling 어떻게?
// promise에서 성공적인 경우를 then을 이용해서 다뤘다면 Error는 catch를 활용
// catch를 활용해 작성하면 Error가 발생하지 않고, 우리가 받아온 Error가 console에 찍힘

Promise에 then을 호출하게 되면 결국 똑같은 Promise를 return하기 때문에, 그 return된 Promise에 catch를 다시 호출할 수 있음!(return된 Promise에 catch를 등록)

3️⃣ finally 예시
: 성공하든 실패하든 상관없이 어떤 기능을 마지막에 수행하고 싶을때 사용

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('ellie');
    reject(new Error('no Network'));
  }, 2000);
});
promise
  .then((value)=>{
  console.log(value);
})
.catch(error => {
  console.log(error);
})
.finally(() => {
  console.log('finally');
});
// 결과: 실패했을때의 error callback 함수가 실행되고 나서 finally가 호출됨
// 만약 reject 대신 성공한 resolve를 사용하면 'ellie'라는 성공적인 함수가 실행되고 finally가 실행됨

3) Promise chaning

// 1초 후에 1을 전달하는 Promise
const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000)1️⃣
});

fetchNumber
.then(num => num * 2) // 2️⃣ 성공적으로 실행되면 해당 숫자에 곱하기2
.then(num => num * 3) // 3️⃣ 그리고 다시 그 숫자에 곱하기3
.then(num => { // 4️⃣ 그 숫자르 다른 서버에 보내서 다른 숫자로 변환된 값을 받아옴
  return new Promise((resolve, reject) => { // 새로운 Promise를 return, 그러면 새로운 Promise는 다른 서버와 통신
    setTimeout(() => resolve(num -1), 1000);
    // 숫자에 1을 뺀 값을 다시 resolve를 통해서 전달
  });
})
.then(num => console.log(num)); // 5️⃣해당 숫자를 출력

결과: 전달된 1이 2️⃣ 여기서 2배로 곱해짐(2) → 3️⃣ 여기서 다시 3배로 곱해짐(6) → 4️⃣ 여기서 새로운 Promise를 전달함 다시 6에서 1을 뺌(5) 5️⃣ 최종적으로 console에 5가 출력!
최종적으로 걸리는 시간은 1️⃣에서 1초, 4️⃣에서 1초 총 2초가 소요!
📌 then은 값을 바로 전달하거나, 또 다른 비동기인 Promise를 전달해도 됨!

4) Error Handling

1️⃣ 성공적으로 전달하는 경우

const getHen = () => 
  new Promise((resolve, reject) => {
    setTimeout(()=>resolve('🐓'), 1000);
  });

const getEgg = hen => 
  new Promise((resolve, reject) => {
    setTimeout(()=>resolve(`${hen} → 🥚`), 1000);
  });

const cook = egg => 
  new Promise((resolve, reject) => {
    setTimeout(()=>resolve(`${egg} → 🍳`), 1000);
  });

getHen() // 먼저 닭을 받아옴
.then(hen => getEgg(hen)) // 닭을 성공적으로 받으면 전달받은 hen을 이용해서 getEgg 호출
.then(egg => cook(egg)) // 성공적으로 전달 받으면 받아온 egg를 이용해서 cook 호출
.then(meal => console.log(meal)); // 성공적으로 받아오면 받아온 문자열을 출력
// 결과: 총 3초후에 console에 '🐓 → 🥚 → 🍳' 출력

// 코드 정리
getHen()
.then(getEgg)
// callback 함수를 전달시, 받아오는 value를 다른 함수로 바로 하나를 호출하는 경우 생략가능!
// 자동적으로 then에서 받아오는 value를 바로 getEgg 함수에 암묵적으로 전달해서 호출해줌
// !한가지만 받아서 그대로 전달하는 경우만 생략가능!
.then(cook)
.then(console.log);

2️⃣ Error가 생긴 경우

const getHen = () => 
  new Promise((resolve, reject) => {
    setTimeout(()=>resolve('🐓'), 1000);
  });

const getEgg = hen => 
  new Promise((resolve, reject) => {
    setTimeout(()=>reject(new Error(`error! ${hen} → 🥚`)), 1000);
  });

const cook = egg => 
  new Promise((resolve, reject) => {
    setTimeout(()=>resolve(`${egg} → 🍳`), 1000);
  });


getHen()
.then(getEgg)
.then(cook)
.then(console.log)
.catch(console.log);
// 결과: Error: error! 🐓 → 🥚
// getEgg에서 Error가 발생했지만, 해당 Error가 맨밑으로 전달되면서 catch가 잡혀짐

// 만약 getEgg에서 문제가 생기면 다른 재료로 대체하고 싶음(Error를 handling하고 싶음)
getHen()
.then(getEgg)
.catch(error => { // 전달된 error를 잘 처리해서 '🥨'대체
  // getEgg에 문제가 생겨도, 전체적인 Promise chaning에 문제가 발생하지 않도록 땜빵 처리!
  return '🥨';
})
.then(cook)
.then(console.log)
.catch(console.log);
// 결과: 🥨 → 🍳
// 🥚 받아오는건 실패, 대신 🥨 전달해줘서 Promoise chaning이 실패하지 않음
// 특정 부분의 Error를 처리 하고 싶은 경우, 그 부분의 바로 다음에 catch를 작성

처음에 만든 코드 Promise 사용해서 수정

function randomTime() {
  return Math.floor(Math.random() * 10) * 1000;
}

function errorFunction() {
  console.log('재고가 없습니다.');
}

function getFruit(market) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${market} -> fruit`);
    }, randomTime());
  });
}

function getSnack(process) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${process} -> snack`);
    }, randomTime());
  });
}

function getBeverage(process) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${process}-> water`);
    }, randomTime());
  });
}

getFruit('이마트') //
  .then(getSnack)
  .then(getBeverage)
  .then(console.log);

// 결과: 이마트 -> fruit -> snack -> water
profile
코딩으로 세상에 이야기하는 개발자

0개의 댓글