류림스 공간

콜백 지옥을 해결하는 방법 본문

개발지식

콜백 지옥을 해결하는 방법

ryurim 2022. 6. 23. 13:38
반응형

콜백 지옥을 해결하는 방법을 설명해보세요.

 

 

콜백지옥 👀

콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제입니다.
콜백함수를 이용해서 비동기 처리를 해주다가 코드가 깊어지는 것을 말합니다.

일반적으로 콜백 지옥을 해결하는 방법에는 Promise 나 Async를 사용하는 방법이 있습니다.

 

function taskA(a, b, cb) {
  setTimeout(() => {
    const res = a + b;
    cb(res);
  }, 2000);
}

function taskB(a, cb) {
  setTimeout(() => {
    const res = a * 2;
    cb(res);
  }, 2000);
}

function taskC(a, cb) {
  setTimeout(() => {
    const res = a * -1;
    cb(res);
  }, 2000);
}

taskA(1, 2, (res_a) => {
  taskB(res_a, (res_b) => {
    taskC(res_b, (res_c) => {
      console.log("taskC Result : ", res_c);
    });
  });
});

다음과 같이 콜백 함수의 콜백 함수의 콜백 함수를 넣어서 비동기 처리의 결과를 또 다른 비동기 처리의 매개변수로 전달 할 수 있다. 

 

콜백 함수는 주로 이벤트 처리나 서버 통신과 같은 비동기 작업을 제어 하기 위해 사용되지만 콜백 함수를 반복 해서 사용하게 되면, 들여 쓰기 수준이 감당할 수 없을 정도록 깊어지게 된다.


Promise 🍄

프라미스란 비동기 연산이 종료된 이후에 결과를 알기 위해 사용하는 객체입니다. 프라미스를 사용하면, 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있습니다. then()과 catch()문의 체이닝을 통해 값을 반환 받아올 수 있습니다. 

  • Promise : .then으로 함수실행순서를 정할 수 있다. 위의 callback 처럼 함수들이 많아질수록 작성하기가 어렵고 가독성이 떨어집니다.

promise는 언젠가 완료가 되는 작업의 결과값을 담는 상자와 같은 역할 을 합니다.
콜백지옥을 해결하기 위한 Promise는 다음 중 하나의 상태를 가집니다.

  • 대기 ( pending ) : 이행하지도 거부하지도 않은 초기 상태
  • 이행 ( fulfill ) : 연산 성공
  • 거부 ( reject ) : 연산 실패
let firstPromise = new Promise((resolve, reject) => {
  // 비동기작업이 성공인경우 resolve(...), 실패인경우 reject(...)를 호출
  setTimeout(() => {
    resolve('성공~');
  }, 1000);
});

firstPromise.then((successMessage) => {
  // successMessage는 위에서 resolve(...) 호출에 제공한 값
  console.log('우와!' + successMessage);
});

// 우와!성공~

 

Promise 기반 비동기적 함수를 호출하면 그 함수는 Promise 인스턴스를 반환한다.
대기중인 promise는 값과 함께 성공(fulfilled) 할 수도, 실패(rejected) 할 수도 있다.
이행되거나 거부될때 프로미스의 then 메서드에 의해 대기열 (큐) 에 추가된 처리기들이 호출된다.

 

Promise는new Promise 생성자를 통해서 Promise 객체를 만들 수 있습니다.
Promise 생성자 함수는 비동기 처리를 수행 할 콜백 함수를 인수로 전달 받는데, 이 콜백 함수는 resolve reject함수를 인수로 전달받습니다.

const promise = new Promise((resolve,reject) => {
  // Promise 함수의 콜백함수내부에서 비동기 처리를 수행
  if(/*비동기처리성공*/ ){
    resolve('result');
  } else {/*비동기처리실패*/
    reject('fail result');
  }
});

Promise 생성자 함수가 인수로 전달받은 콜백함수내부에서 비동기처리를 수행한다.
이 때 비동기처리가 성공하면 콜백함수의 인수로 전달받은 resolve함수를 호출하고, 실패하면 reject함수를 호출한다.

프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속 처리를 해야한다.
예를들어, 프로미스가 fulfilled상태가 되면 프로미스의 처리결과를 가지고 무언가를 해야하고, rejected상태가되면 프로미스의 처리결과(에러)를 가지고 에러처리를 해야한다.
이를 위해 프로미스는 후속처리메서드 then, catch, finally를 제공한다.
즉, 프로미스의 비동기 처리상태가 변화하면 후속처리메서드에 인수로 전달한 콜백함수가 선택적으로 호출된다. 

 

Promise.prototype.then
then 메서드는 두개의 콜백함수를 인수로 전달받는다.
첫번째 콜백함수는 비동기처리가 성공했을 때 호출되는 성공 처리 콜백함수고, 두번째 콜백함수는 비동기처리가 실패했을 때 호출되는 실패 처리 콜백함수다.
then 메서드는 언제나 프로미스를 반환한다.

 

Promise.prototype.catch
catch 메서드는 한개의 콜백함수를 인수로 전달받는다.
catch 메서드의 콜백함수는 프로미스가 rejected상태인 경우만 호출된다. ( 에러처리를 해줄 때 사용한다. )
또한 then 메서드와 동일하게 동작하기때문에, 언제나 프로미스를 반환한다.

 

Promise.prototype.finally
finally 메서드는 한개의 콜백함수를 인수로 전달받는다.
프로미스의 상태와 상관없이 무조건 한 번 호출된다.
즉, finally메서드는 프로미스 상태와 상관없이 공통적으로 수행해야 할 처리내용이 있을 때 유용하다.

Pending(대기)

아래와 같이 Promise 객체를 생성하면 대기(Pending) 상태입니다.

new Promise();

아래와 같이 new Promise() 메서드 호출 시 콜백 함수를 선언할 수 있으며, 인자는 resolve와 reject입니다.

new Promise((resolve, reject) => {});

Fulfilled(이행)

아래와 같이 콜백 함수 인자인 resolve를 실행하면 이행된(Fulfilled) 상태입니다. 참고로, 이행된 상태를 다른 말로 완료된 상태라고도 합니다.

new Promise(function(resolve, reject) {
  resolve();
});

이행 상태가 되면 then()을 활용하여 결괏값을 받을 수 있습니다.

 

async, await

ES8에서 도입된 async , await를 사용하면 비동기함수를 마치 동기적 코드인거처럼 동작하도록 구현할 수 있다.
즉, 프로미스의 후속처리 메서드(then, catch, finally) 없이 마치 동기 처리처럼 프로미스가 처리결과를 반환하도록 구현할 수 있다.

비동기함수는 항상 promise객체를 반환한다는 특징이 있다.
async함수는 async키워드를 사용해 언제나 프로미스를 반환한다.
async함수가 명시적으로 프로미스를 반환하지 않더도 async함수는 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.

async는 asynchronous의 줄임말로 비동기를 의미한다. 이 함수 안에 비동기적으로 실행될 부분(await)이 있다는 것을 의미한다.

 

async function test() {
    // logic
}

변수 = async() => {
	// logic
}
  • await는 async 안에서만 작동한다. await 키워드를쓰게되면 해당값이 반환 되기 전까지 기다리는 동안 async 내부 함수는 일시 중단이 된다.
    ( 프로미스가 settled상태 ( 비동기처리가 수행된 상태 )가 될때까지 대기하다가 settled상태가 되면 프로미스가 resolve한 처리결과 값을 반환한다.
function userInfo() {
  const url = 'https://koreanjson.com/users/1';
  return fetch(url)
    .then((res) =>{
    return res.json();
  })
}

function userTodo() {
  const url = 'https://koreanjson.com/todos/1';
  return fetch(url)
    .then((res) => {
    return res.json(); 
  })
}

async function sumOfFetch() {
  const user = await userInfo();
  if(user.id === 1) {
   const todo = await userTodo();
   console.log(todo)
   return todo;
  }
}
sumOfFetch();

 

async, await 예외처리 try catch 문

error 처리를 할 경우에는 정상적으로 동작 되었을 때 실행되는 부분을 try 문으로 감싸고 err가 발생 시 실행되는 부분을 catch문으로 감싼다.

async function sumOfFetch() {
  try{
    const user = await userInfo();
    if(user.id === 1) {
    const todo = await userTodo();
    console.log(todo)
    return todo;
    }
  } catch (e) {
    console.error(e);
  }
}
sumOfFetch();

 

  • async, await : 꼭 async 가 있어야 await 을 쓸 수 있고, promise 방법보다 작성하기 쉽고 가독성도 올라간다. 다만 아직 완전히 해결되는건 아니므로 callback과 promise를 적절히 섞어쓰면 된다.

 

참고: https://backback.tistory.com/319 / https://velog.io/@5o_hyun/%EC%BD%9C%EB%B0%B1%EC%A7%80%EC%98%A5%ED%95%B4%EA%B2%B0-Promise-async-await

728x90
반응형
Comments