TIL: 2022-06-02 React 숙련

김하연·2022년 6월 2일
0

TIL: Today I Leaned

목록 보기
19/26

콜백함수란?

간단히는 함수에 파라미터로 들어가는 함수라고 볼 수 있으며, 코드들을 순차적으로 실행하고 싶을 때 사용한다. 모든 곳에서 콜백함수를 사용할 수 있는 것은 아니고, 콜백함수가 필요한 함수들 안에서만 사용이 가능하다.

document.querySelector('.button').addEventListener('click', function(){
	// 실행할 내용
})
// addEventListener 는 함수
// 그 안에 들어간 function도 함수 => 콜백함수

setTimeout(function()=>{
	// 실행할 내용    
}, 1000)
// setTimeout 는 함수
// 그 안에 들어간 function도 함수 => 콜백함수

아래와 같은 방법으로 함수 안에 함수를 넣어 콜백함수를 만들 수 있다.

function first(param){
  param() // 콜백함수
}
function second(){
  // 실행할 내용
};

first(function(){}) // 사용방법 1. 바로 function 내용 작성 가능
first(second) // 사용방법 2. 만들어둔 함수를 파라미터에 넣어서 사용 가능

위와 같이 작성할 경우 second라는 함수를 first 안에 집어넣어 순차적으로 실행할 수 있는 구조가 되었다. 즉, 콜백함수는 어떠한 코드들을 내가 원하는 순서로 코드를 실행하고 싶을 때 사용한다.

function first(param){
  console.log(1);
  param();
}
function second(){
  console.log(2);
};

first(second)

first 함수를 먼저 실행한 뒤 second 함수를 실행하고 싶을 경우, 위와 같이 콜백함수를 사용하여 순차적으로 실행시킬 수 있다.

function first(param){
  console.log(1);
}
function second(){
  console.log(2);
};

first();
second();

그러나, 위와 같이 굳이 콜백함수를 사용하지 않아도 함수를 실행시키는 코드를 순차적으로 배치해도 같은결과를 얻을 수 있는데.. 그렇다면 굳이 콜백함수를 쓰는 이유가 무엇일까?!

예를 들어 다른 사람들과 협업을 한다고 했을 때,
누군가는 first() 함수에 바로 이어서 console.log(4)를 출력하고 싶을수도 있고 누군가는 first() 함수에 바로 이어서 console.log(3)을 출력하고 싶은 경우가 발생할 수 있다.
그럴 경우 first() 함수 아래에 이어서 각자 원하는 console.log(원하는 값)를 실행하면 되겠지만, 이 결과가 항상 first()함수가 호출된 후 다음으로 작성된 코드가 차례로 출력된다는 보장은 없다.
first() 함수가 비동기 처리가 되어있을 경우에도 그런 상황이 발생할 수 있다.
그렇기 때문에 콜백함수를 사용하면 예외 상황을 방지할 수 있고, 코드의 흐름을 의도하는대로 제어할 수 있기에 유용하게 사용된다.

db.collection('post').findOne(A, function(){
	db.collection('post').findOne(B, function(){
        db.collection('post').findOne(C, function(){
            .....
        })
  	})
})

그러나 콜백함수 또한 과도한 사용은 비효율적인 결과를 발생시킬 수 있다.
예를 들어 위와 같이 DB에서 A데이터를 뽑고, 그 다음에 이어서 B데이터를 뽑고, 이어서 C데이터를 뽑고 싶을 경우 콜백함수를 사용하면 위와 같이 작성된다.

그러나 이런 이런 콜백함수가 계속해서 nesting되어 형태가 길어지고 복잡해질 경우 코드의 가독성이 떨어지고, 어떻게 코드들이 서로 연결되어있는지 로직을 한 눈에 보고 이해하기가 힘들어진다. 때문에 디버깅하고 문제를 분석하는 것 또한 어려워져 유지보수에도 유용하지 않다.




async, await

promise 함수가 등장한 배경

function loadFruit(){
	return 'apple'; // 불러오는데에 10초가 걸리는 데이터
}

const fruit = loadFruit();
console.log(fruit)
.
.
.

위 코드에서 LoadFruit 함수 내에 있는 'apple' 이라는 데이터가 호출되는데에 총 10초가 걸린다고 가정한다면, loadFruit 함수가 처리되는 10초라는 시간동안 그 아래에 작성되어 있는 모든 코드들은 실행되지 못하고 멈춰있게 된다. 이런 비효율적인 상황을 방지하기 위해 promise라는 함수가 등장했다고 한다.

promise 사용 방법

function loadFruit(){
  return new Promise((resolve, reject)=>{
  	resolve('apple');
  })
}
const fruit = loadFruit();
fruit.then(console.log); // resolve 값이 출력됨
console.log(fruit)

promise보다 간편한 async

async는 promise를 좀 더 간단히 사용할 수 있는 방법이다.
async는 항상 함수 앞에 위치하는데, function 앞에 async를 붙이면 해당 함수는 항상 promise를 반환한다. promise가 아닌 것은 promise로 감싸 반환한다.

async function loadFruit(){
  return ('apple');
} // 자동적으로 함수 안에 있는 코드 블럭들이 promise로 변환된다.

const fruit = loadFruit(); // loadFruit()은 promise를 반환
fruit.then(console.log);
console.log(fruit)

async라는 키워드를 함수 앞에 쓰면 코드블럭이 자동으로 promise로 변환되기 때문에 위의 예제 결과도 promise형태인 것을 볼 수 있다.

더욱 유용한 await

awaitasync 함수 안에서만 동작한다.
await는 말 그대로 promise가 처리될 때까지 함수 실행을 기다리게 만든다. promise가 처리되면 그 결과와 함께 실행이 재개된다. promise가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다.

function delay(ms){
  return new Promise(resolve => setTimeout(resolve, ms));
} // delay함수는 ms의 시간이 지나면 resolve를 호출하는 promise를 리턴

async function getApple(){ // 3초가 지나면 resolve를 호출하는 promise 전달
  await delay(3000); // delay(300) 끝날때까지 기다려!
  return 'Apple'; // 3초 기다린 후 'Apple' 리턴
}

async function getBanana(){ // 3초가 지나면 resolve를 호출하는 promise 전달
  await delay(3000); // delay(300) 끝날때까지 기다려!
  return 'Banana'; // 3초 기다린 후 'Banana' 리턴
}

/* -------------- getBanana를 promise로 작성할 경우 -------------- */

function getBanana(){
  return delay(3000)
  .then(()=>'banana');
} 
// 이렇게 체이닝 방식으로 작성하는 것보다 await을 사용하는게 이해하기 더 쉽고 직관적이다.

위 코드를 promise로 출력할 경우

function pickFruits(){
  return getApple()
  	.then(apple => {
    	return getBanana()
      		.then(banana => {`${apple} + ${banana}`});
  	})
}
pickFruits().then(console.log); // 이럴 경우 콜백지옥 발생

위 코드를 await으로 출력할 경우

async function pickFruits(){
  const apple = await getApple();
  const banana = await getBanana();
  return `${apple} + ${banana}`;
}

pickFruits().then.(console.log); // 좀더 간결해진 호출 방식

await 병렬 처리하기

/* -------------- 병렬처리되지 않은 코드 -------------- */
async function pickFruits(){
  const apple = await getApple(); // apple 호츌 1초 기다렸다가
  const banana = await getBanana(); // apple 1초 완료되면 바나나 호출
  return `${apple} + ${banana}`;
}

/* -------------- 병렬처리된 코드 -------------- */
async function pickFruits(){
  // 함수를 생성했기 때문에 만들자마자 안의 코드가 실행됨.
  const applePromise = getApple();
  const bananaPromise = getBanana();
  
  const apple = await applePromise(); 
  const banana = await bananaPromise();
  return `${apple} + ${banana}`;
  // 동시에 실행된 코드를 기다렸다가 return 값 출력됨.
  // apple을 만드는데 banana가 필요 없고 banana를 만드는데 apple이 필요하지 않으므로
}

Promise API 사용하여 병렬 처리하기

function pickAllFruits(){
  return Promise.all([getApple(), getBanana()]) // promise 배열을 전달하면 모든 promise들이 병렬적으로 다 받을때까지 기다린다.
}

pickAllFruits().then(console.log);

먼저 끝나는 Promise 하나만 받기

function pcikOnlyOne(){
  return Promise.race([getApple(), getBanana()]) // promise 배열을 전달하면 가장 먼저 값을 return하는 promise만 전달
}

pcikOnlyOne().then(console.log);

그 외 async, await 에 대한 추가 정보

Error 처리를 하고싶다면?

async function f() {
  try {
    let response = await fetch('http://유효하지-않은-주소');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

await은 최상위 레벨 코드에서 작동하지 않는다.

// 최상위 레벨 코드에선 문법 에러가 발생함
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

//하지만 익명 async 함수로 코드를 감싸면 최상위 레벨 코드에도 await를 사용할 수 있다.
(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();
  ...
})();

async/await는 Promise.all과도 함께 쓸 수 있다.

여러 개의 프라미스가 모두 처리되길 기다려야 하는 상황이라면 이 프라미스들을 Promise.all로 감싸고 여기에 await를 붙여 사용할 수 있다.

// 프라미스 처리 결과가 담긴 배열을 기다립니다.
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

0개의 댓글