[코어자바스크립트 스터디] - 4

Empu·2024년 8월 12일

javaScript

목록 보기
3/5
post-thumbnail

04. 콜백함수

어떤 함수에 인자로 전달되는 함수들

콜백함수의 제어권 위임

  • 호출 시점
    • 콜백함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다
  • 인자
    • 컴퓨터는 인자를 이미 정해진 ‘순서’에 의해서만 각각 구분하고 인식한다.
    • 콜백함수를 호출 할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다
      • ex) map함수의 첫번째 인자는 currentValue, 두번째는 index.
  • this
    • 콜백함수의 this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있다.
    • 정하지 않은 경우에는 전역객체 바라본다.
    • 임의로 바꾸고 싶다면 bind 메서드 이용하기

콜백지옥과 비동기 제어

  • 콜백지옥 - 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들정도로 깊어지는 현상.
  • 동기 - 특정 코드를 수행한 후 다음 코드를 실행
  • 비동기 - 특정 코드를 수행하는 도중 다음 코드를 실행(별도의 요청, 실행 대기,보류 등과 관련된 코드)
function exercise(callback) {
  setTimeout(() => {
    console.log('Exercising...');
    callback(null, 'Exercise done');
  }, 1000);
}

function eat(callback) {
  setTimeout(() => {
    console.log('Eating healthy food...');
    callback(null, 'Meal done');
  }, 1000);
}

function sleep(callback) {
  setTimeout(() => {
    console.log('Sleeping...');
    callback(null, 'Sleep done');
  }, 1000);
}

// 콜백 지옥 예시
exercise((err, exerciseResult) => {
  if (err) {
    console.error(err);
  } else {
    console.log(exerciseResult);
    eat((err, mealResult) => {
      if (err) {
        console.error(err);
      } else {
        console.log(mealResult);
        sleep((err, sleepResult) => {
          if (err) {
            console.error(err);
          } else {
            console.log(sleepResult);
            console.log('All health activities done!');
          }
        });
      }
    });
  }
});
  • 기명함수를 사용하여 어느정도 가독성을 확보했지만 여전히 가독성이 떨어진다.

Promise 사용하기

  • 결과(promise 객체)를 반환하겠다는 약속
  • 프로미스를 생성하여 반환하는 함수를 '프로미스 팩토리 함수' 라고 부른다.
  • new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백함수는 호출할 때 바로 실행되지만 그 내부에 resolve,reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지 다음 or 오류 구문으로는 넘어가지 않는다
  • 일반적으로  resolve() 함수의 인자로는 미래 시점에 얻게될 결과를 넘겨준다
  • 일반적으로 reject() 함수의 인자로는 미래 시점에 발생할 예외를 넘겨준다
  • Promise는 3가지 상태 중 하나를 가진다
    • Fulfilled: 성공적으로 처리가 완료된 상태, onFulfilled()가 호출된다 (ex) resolve() 호출)
    • Rejected: 처리가 실패로 끝난 상태, onRejected()가 호출된다 (ex) reject() 호출)
    • Pending: 처리가 완료되지 않은 상태, 아직 fulfilled 또는 rejected 상태가 아님
    • Promise가 pending 상태가 아니면, settled 상태라고 말한다

Promise 생성자안에 들어가는 콜백 함수를 executor 라고 부른다.

const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
    	resolve("처리 완료")
    }, 5000)
});
console.log(promise); // Pending (대기) 상태
  • Promise 핸들러
then() : 프로미스가 이행(fulfilled)되었을 때 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환.
catch() : 프로미스가 거부(rejected)되었을 때 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환.
finally() : 프로미스가 이행되거나 거부될 때 상관없이 실행할 콜백 함수를 등록하고, 새로운 프로미스를 반환
  • Promise chaining

function exercise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Exercising...');
      resolve('Exercise done');
    }, 1000);
  });
}

function eat() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Eating healthy food...');
      resolve('Meal done');
    }, 1000);
  });
}

function sleep() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Sleeping...');
      resolve('Sleep done');
    }, 1000);
  });
}

// Promise chaining 예시
exercise()
  .then(result => {
    console.log(result);
    return eat();
  })
  .then(result => {
    console.log(result);
    return sleep();
  })
  .then(result => {
    console.log(result);
    console.log('All health activities done!');
  })
  .catch(err => {
    console.error(err);
  })
  .finally(()=>{
	  console.log("+++")
  })
  
 
 //finally는 이행된 값과 결과에 상관없이 실행되기에 인자를 받지 않는다 

Async/Await 사용하기

  • 비동기 작업을 수행하고자 하는 함수 앞에 async를 표기한다
  • 함수 내부에서 실질적으로 비동기 작업이 필요한 곳에 await를 표기하여 뒤 내용을 자동으로 Promise로 전환한다
  • async의 리턴값 또한 결국 Promise객체
// exercise, eat, sleep 함수들은 위의 예제 코드와 동일하다는 가정. 

async function performHealthActivities() {
  try {
    const exerciseResult = await exercise();
    console.log(exerciseResult);

    const eatResult = await eat();
    console.log(eatResult);

    const sleepResult = await sleep();
    console.log(sleepResult);

    console.log('All health activities done!');
  } catch (err) {
    console.error(err);
  }
}

performHealthActivities();

async/await 보다 promise를 사용하면 유리한 경우

  • async / await은 무조건 좋음? → ㄴㄴ

  • async/await은 Promise의 then 체인(가독성 문제)를 어느정도 보완해주었다

  • 하지만 다수의 await의 사용은 성능에 문제를 줄 수 있다. → await 병목현상

  • await 병목 현상

    • api를 연속으로 몇개씩 호출을 해야 할때, 속도가 느려질 수 있다. → promise.all()로 처리 가능
    • promise.all() 은 여러 프로미스들을 병렬적으로 실행하고, 가장 마지막 태스크가 완료될 때 완료 상태를 반환한다.
    • 주의 - 내부 프로미스의 순서들이 제어되지 않기 때문에, 프로미스의 실행 순서가 중요한 경우에는 사용하면 안된다. 즉 함수들 간의 의존성이 없을 때 이용하면 상당히 좋은 시간 절감 효과를 볼 수 있다.
// exercise, eat, sleep 함수들은 위의 예제 코드와 동일하다는 가정. 

async function performHealthActivities() {
  try {
    const exerciseResult = await exercise();
    console.log(exerciseResult);

    const eatResult = await eat();
    console.log(eatResult);

    const sleepResult = await sleep();
    console.log(sleepResult);

    console.log('All health activities done!');
  } catch (err) {
    console.error(err);
  }
}

performHealthActivities();

//위 코드에서 exercise(), eat(), sleep() 함수가 각각 1초씩 걸린다고 가정하면,
//전체 실행 시간은 3초. 이는 각 작업이 순차적으로 실행되고, 다음 작업이 시작되기 전에 이전 작업이 완료될 때까지 기다리기 때문.
  • Promise.all() 사용
// exercise, eat, sleep 함수들은 위의 예제 코드와 동일하다는 가정. 
async function performHealthActivities() {
  try {
    const results = await Promise.all([exercise(), eat(), sleep()]);
    results.forEach(result => console.log(result));
    console.log('All health activities done!');
  } catch (err) {
    console.error(err);
  }
}

performHealthActivities();

//하나라도 실패하면 promise는 catch 블록 실행.
//각 작업은 1초가 걸리기에 전체 실행시간도 1초로 줄어들 수 있다.

→ !? 하나라도 실패하면 catch가 실행되는게 걸린다면?

  • Promise.allSettled() 사용 → 여러 프로미스를 병렬적으로 처리하되, 하나의 프로미스가 실패해도 무조건 이행한다.
    • 반환된 각 객체별로 status를 확인할 수 있다
    • 만약 fulfilled 상태라면 value를, rejected 상태라면면 reason 속성을 확인할 수 있다
async function performHealthActivities() {
  try {
    const results = await Promise.allSettled([exercise(), eat(), sleep()]);
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log(result.value);
      } else if (result.status === 'rejected') {
        console.error(result.reason);
        //실패에러 발생 시 다시 요청을 보내는 로직 작성 가능.
      }
    });
    console.log('All health activities done!');
  } catch (err) {
    console.error(err);
  }
}

performHealthActivities();
profile
Life is a risk.

0개의 댓글