39일차 (02-01-2021)

조상래·2021년 2월 1일
0

코드스테이츠

목록 보기
37/73
post-thumbnail

자바스크립트를 배우면서 가장 궁금했던 비동기의 callback/ promise/ async&await 이 세가지의 키워드에 대해서 배웠다. 처음 보는 애들이라 조금 생소했고 특히 콜백함수를 사용해야하는 부분은 할 때마다 어렵고 복잡하다. 아직은 이걸 왜 써야하나 비동기가 왜 중요하나 이런 부분에 대해선 직접 경험하기 전까진 정확히 감이 오지 않는다. 오늘은 비동기가 필요한 간단한 상황과 키워드의 사용법에 대해 배운대로 설명해보겠다.

1. 비동기

클라이언트가 서버에 요청을 했을 때 데이터를 받아 오는 시간 동안 우린 다른 작업을 할 수 있어야한다. 그러나 만약 이 과정이 동기로 이루어진다면 요청을 받아올 때까지 다른 작업을 할 수 없을 것이다. 간단하게 예를 들어보자.

비동기의 대표적인 예로는 setTimeout이 있다. 이를 이용해 상황을 구현해보자.

const dosomething = () => {
  console.log('Do something');
}

const request = () => {
  console.log('Request successfully');
  receive();
}

const receive = () => {
  console.log('Receive successfully');
}

무엇인가를 하는 dosomething 함수와 서버에 요청하는 request 그리고 데이터를 받아 오는 receive가 있다. 위는 간단하게 동기의 상황을 구현해 본것이다.

dosomething();
dosomething();
request()
dosomething();
dosomething();


우리가 인터넷을 켜서 무엇인가를 한다고 해보자 간단하게 유튜브를 본다고 가정하면 우린 request를 통해 영상을 받아오게 될 것이고 영상을 받아오고 나서야 다른 일을 할 수 있다.

즉, 우리는 이러한 상황을 피하기 위해 비동기를 이용하는데,

const dosomething = () => {
  console.log('Do something');
}

const request = () => {
  setTimeout(() => {
    console.log('Request successfully');
    receive();
  }, 1000)
}

const receive = () => {
  setTimeout(() => {console.log('Receive successfully')}, 2000);
}

dosomething();
dosomething();
request()
dosomething();
dosomething();

요청한 순서와는 상관없이 dosomething이 실행되는걸 알 수 있다.

비동기의 필요성에 대해서 알아보았다. 그렇다면 비동기화(?)하는 방법은 어떤게 있을까?

2. callback

class Request {
  selectVideo(name, success, error) {
    setTimeout(() => {
      if (name === 'video') {
        success(name);
      } else {
        error(new Error('not found'));
      }
    }, 1000)
  }
};

const request = new Request();
request.selectVideo('video',
  receive => {
    console.log(`The ${receive} is played!`)
  },
  error => {
    console.log(error);
  }              
);

위는 Request라는 생성자 함수의 메소드 selectVideo를 실행할 때 매개변수 name에 원하는 비디오를 이름을 넣고 재생을 시키는 형태이며, video라는 정확한 이름을 받을 때 (즉, 부합하는 데이터가 있을 때) succes를 콜백으로 받아 name을 전달해주고, 없다면 에러를 출력하는 형태이다.

여기서 조금 더 심화 하면,

class Request {
  selectVideo(name, success, error) {
    setTimeout(() => {
      if (name === 'video') {
        success(name);
      } else {
        error(new Error('not found'));
      }
    }, 1000)
  }

  playedAt(name, success, error) {
    setTimeout(() => {
      if (name === 'video') {
        success({name: 'video', date: new Date()});
      } else {
        error(new Error('Try again'));
      }
    }, 1000)
  }
}

const request = new Request();
request.selectVideo('video',
  receive => {
    console.log(`The ${receive} is selected`);
    request.playedAt(receive, 
      time => { 
        console.log(`The ${time.name} is played at ${time.date}`)
    },
    error => {
      console.log(error);
    });
  },
  error => {
    console.log(error);
  } 
);

playedAt이라는 메소드를 추가하여 비동기를 체이닝해 보았다.

그러나 지금 이 간단한 코드 몇줄을 쓰는데에도 복잡하고 어지럽다. 만약 비동기 호출이 많아질 경우는 어떻게 될까? 코드는 상당히 가독성이 떨어질 것이며 만약 중간에 실수라도 한다면 정말 끔찍할 것이다. 우린 이러한 상황을 '콜백지옥' 이라고 한다. 콜백지옥을 피하기 위해 promise 키워드를 사용해볼것이다.

3. promise

먼저 promise 키워드의 특징부터 보자.

const shoppingCart = new Promise((resolve, reject) => {
    console.log('When created');
  });
// 콘솔창에 When created가 출력.

promise는 제대로 실행되었을때의 resolve와 제대로 실행 되지 않았을 때에 reject 이 두가지의 콜백함수를 매개변수 지정한다. 그리고 한가지 특징은. 위처럼 promise는 생성 되자마자 실행이 되는데 우리가 이를 원할 때 받아오고 싶으면 어떻게 해야할까?

바로 then 키워드를 사용하면 된다.

const shoppingCart = new Promise((resolve, reject) => {
    resolve('Add');
  });


shoppingCart.then((item) => console.log(item + ' apple')); // Add apple


// 아래와 같이 함수로 리턴하여 생성시 호출 없이 생성할수도 있다. 
// 아래의 케이스는 에러가 있을 때를 가정하여 reject와 catch를 사용한 것이다.
const shoppingCart = () => {
  return new Promise((resolve, reject) => {
    reject(new Error('Already full!'));
  })
};

// catch 키워드는 앞의 then이 여러개 있더라도 에러가 나면 무시하고 catch가 실행된다.
// 그 외 finally라는 키워드가 있는데 이는 에러가 있든 없든 무조건 실행되게 하는 키워드이다.
shoppingCart()
  .then(item => console.log(item + ' apple'))
  .catch(error => console.log(error)); // Error: Already full!

then 키워드로 원할 때 promise를 호출할 수 있으며 체이닝도 간편하게 가능하다.

const shoppingCart = new Promise((resolve, reject) => {
    resolve('Add');
  });


shoppingCart
.then(item => item + ' apple,')
.then(item => item + ' banana,')
.then(item => item + ' orange')
.then(item => console.log(item)); // Add apple, banana, orange.

그럼 이를 이용해 callback에서 만들었던 코드를 promise로 바꿔보자.

class Request {
  selectVideo(name) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (name === 'video') {
          resolve(name);
        } else {
          reject(new Error('not found'));
        };
      }, 1000)
    });
  };

  playedAt(name) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (name === 'video') {
          resolve({name: 'video', date: new Date()});
        } else {
          reject(new Error('Try again'));
        };
      }, 1000)
    });
  };
};

Promise를 리턴하게 바꾸어 주었으니 Promise 매개변수로 오는 콜백함수 resolve와 reject로 교체해주었다.

const request = new Request();
request.selectVideo('video')
  .then((name) => {
    console.log(`The ${name} is selected`)
    return name;
  })
  .then(name => request.playedAt(name))
  .then(time => console.log(`The ${time.name} is played at ${time.date}`));

// 아래와 같이 써도 됨.
const request = new Request();
request.selectVideo('video')
  .then((name) => {
    console.log(`The ${name} is selected`);
    return request.playedAt(name);
  })
  .then(time => console.log(`The ${time.name} is played at ${time.date}`));

callback에서 호출하던 과정이 굉장히 복잡했는데 더욱 간단하게 줄어 든 것을 볼 수 있다. 그리고 여러가지 promise를 한번에 호출하여 관리할 수 있는 Promise.all 메소드도 있으니 찾아보길 바란다.

이러한 promise도 .then의 사용빈도가 높아진다면 '프로미스지옥'이 발생할 수 있다. 이를 방지하기 위한 방법이 있다.

4. async & await

위의 코드를 async와 await를 이용해 구현해보자.

const request = new Request();

const withAsync = async () => {
  const name = await request.selectVideo('video');
  console.log(`The ${name} is selected from promise`);
  const time = await request.playedAt(name);
  console.log(`The ${time.name} is played at ${time.date} from promise`);
}

withAsync();

조금은 더 간소화 된걸 알 수 있다. async의 주의점은 꼭 명시를 해줘야 한다는 것.

이 외에도 다양하게 활용할 수 있으며 자세한 활용법에 대해선 공부해보고 적용해보면서 익혀나가야겠다.
오늘은 맛보기 정도로 배웠지만, 벌써부터 유용함의 냄새가 난다. 더욱 깊게 공부하고 싶지만 시간이 부족하다는게 마음이 아프다. 내일은 이를 이용해 fetch API를 해보는 스케줄이있다. 내일은 이 키워드들의 유용함을 조금 더 알수있는 시간이 될것같다.

profile
Codestates Full IM26기 수료

0개의 댓글