IT/React

React 웹캠 - 1. Promise 비동기 함수의 이해

루벤초이 2021. 3. 29. 20:48

React 웹캠 시리즈입니다.

Sample Code


웹캠 사용 코드는 React뿐만 아니라 일반 자바스크립트에서도 사용할 수 있는 코드인데요,

다만 이번 예제에서 React 앱을 연동하기 위해 React state/ref 등을 사용합니다.

 

예제 코드: Sample Code
실행 방법: npm start 후 브라우저에서 http://localhost:3000/test03 으로 접속
소스 위치: src/ui/TestWebcam.js

 

본격적으로 웹캠 코드를 논하기 전에, 자바스크립트의 비동기 작업의 미래 결과인 Promise에 대해 이해할 필요가 있습니다.

웹캠 코드가 비동기 함수, 즉 함수를 호출하는 즉시 리턴하고 결과는 나중에 알려주는 함수이기 때문입니다.

 

Promise, 비동기 작업의 미래 결과

  • Promise 의미를 이해하기 보다는, 사용법을 익히는 편이 낫습니다.
  • 아래 일반적인 Promise 코드 예제가 있는데요, go() 함수의 Line 1~5은 myPromise라는 Promise 객체를 생성한 것이고 나머지 라인은 생성된 myPromise 객체를 실행하는 부분입니다. 여기서 기계적으로 외워야 할 부분은 resolve(m)를 호출해야만 .then(m)이 호출된다는 사실입니다.
<html>
<script>
function go(){
  let myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Success!");
    }, 1000);
  });

  myPromise.then((message) => {
    document.getElementById('result').innerHTML += '<p>2. Then : ' + message+'</p>';
  });

  document.getElementById("result").innerHTML += '<p>1. Finished</p>';
}
</script>
<body onload='go();'>
<div id="result"></div>
</body>
<html>
  • 즉, 위 코드를 html 파일로 저장하고 브라우저에서 열면,  다음과 같이 "1. Finished"가 찍히고 1초 후에 "2. Then : Success!"가 찍힙니다.

  • 원리는, html 파일이 로딩되면 body의 onload 함수로 지정된 go()가 불리고,
  • 함수의 Line 7에서 myPromise의 setTimeout을 실행한 뒤 .then 절을 무시하고 Line 11의 "1. Finished"가 찍힙니다.
  • 1000ms(1초) 후 setTimeout이 expired됨에 따라 setTimeout 함수 안에 있던 resolve가 불리면 그때, then() 안의 코드가 불리면서 "2. Then: Success!"가 찍히게 됩니다.

 

Promise + async/await, 비동기에서 동기로

  • 만일 setTimeout처럼 콜백을 호출하는 비동기 함수를 여러번 중첩해서 쓰게 되면, 이른바 콜백지옥에 빠질 수 있습니다.
func1(() => {
  func2(() => {
    func3(()=>{
      func4(()=>{
        console.log("welcome to callback hell");
      })
    })
  })
})
  • 이런 구조가 깊어질수록 가독성이 떨어지고 버그가 많이 생기겠죠.
  • 해결방안은 비동기 함수를 동기 함수, 즉 바로 결과를 리턴하는 함수로 만드는 방법입니다. 다시 말해, 결과를 리턴할 때까지 기다리는 거죠.
  • 문법은 Promise 객체 앞에 await 키워드를 붙이고 현재 함수에 async 키워드를 붙이는 것인데, 아래 코드를 보고 형식을 외워버립시다.
<html>
<script>
async function go(){
  let myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Success!");
    }, 1000);
  });

  const message = await myPromise;
  document.getElementById('result').innerHTML += '<p>2. Then : ' + message+'</p>';

  document.getElementById("result").innerHTML += '<p>1. Finished</p>';
}
</script>
<body onload='go();'>
<div id="result"></div>
</body>
<html>
  • 결과가 예상되나요?
  • 그렇습니다. 이번엔 결과가 2, 1 순으로 나오죠. 왜냐하면 Line 7에서 await으로 기다렸다가 메시지2부터 찍고 난 다음 메시지1을 찍었기 때문입니다.
  • 주의할 점은 go() 정의에 async 키워드가 붙어있는 점인데, 이를 람다 형식으로 정의할 때는 다음과 같이 정의합니다.
    • const go = async() => {

 

Promise.all(), 모두 기다리기

  • 마지막으로 살펴볼 것은 여러 Promise 객체를 동시 다발적으로 수행할 때 모든 작업이 완료되기를 기다리는 구문입니다.
  • 예제 코드를 살펴봅시다.
<html>
<script>
async function go(){
  let myPromise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Success!");
    }, 1000);
  });

  let myPromise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Again!");
    }, 3000);
  });

  Promise.all([myPromise1, myPromise2]).then((messageArray)=>{
    document.getElementById('result').innerHTML += '['+messageArray[0]+', '+ messageArray[1]+']';
  });
  document.getElementById("result").innerHTML += '<p>1. Finished</p>';
}
</script>
<body onload='go();'>
<div id="result"></div>
</body>
<html>
  • myPromise1, myPromise2 두 개의 객체가 있을 때 이 두 Promise가 모두 종료되고 나서 진행하겠다는 의미로 .then 대신 .all() 함수를 사용합니다. 이때 전달되는 값은 각 Promise의 resolve 결과값들의 배열입니다.
  • 한 번 따라서 타이핑해보고 실행해 보면 이해도 하기 전에 익숙해질 거에요.
  • 다음 시간에는 웹캠에 접근하는 getUserMedia에 대해 알아봅시다.
728x90
반응형