본문 바로가기
-

JavaScript / React에서 sync / async(동기 비동기) 관련 상식

by dong_su 2023. 12. 23.

자바스크립트의 sync / async 관련 상식

자바스크립트는 일반적인 코드를 작성하면 synchronous 하게 처리됩니다.

번역하면 동기방식인데 코드 적은 순서대로 윗줄부터 차례로 코드가 실행된다는 뜻입니다.

거의 모든 프로그래밍 언어들은 위에서 부터 한줄한줄 실행됩니다.


예를 들어 

console.log(1+1)
console.log(1+2)
console.log(1+3)

 

이런 코드는 그냥 위에서부터 한줄한줄 잘 실행됩니다.

그니까 콘솔창에 2, 3, 4 순으로 출력된다는 소리입니다. 

뭔가 당연한 소리를 하고 있습니다. 

자바스크립트는 ajax, 이벤트리스너, setTimeout 이런 함수들을 사용하면 asynchronous하게 코드 실행이 가능합니다.

번역하면 비동기적인데 이런 함수들은 처리 시간이 걸립니다.

그래서 ajax 요청하는 코드들은 순차적으로 실행되지 않습니다.


예를 들어

console.log(1+1)
axios로 get요청하고나서 console.log(1+2) 실행
console.log(1+3)

 

이런 코드는 2, 4가 바로 출력되고 그 다음에 3이 출력됩니다.

3을 출력하는 코드가 asynchronous 처리를 지원하는 코드라 그렇습니다. 

3을 출력할 때 오래걸리면 완료될 때 까지 잠깐 보류했다가 다른 코드를 먼저 실행시킨다는 소리입니다. 

심지어 ajax요청이 0.00초 걸려도 2, 4가 먼저, 그 다음 3이 출력됩니다. 물리적으로 잠깐 처리가 보류되어서 그렇습니다. 

자바스크립트라는 언어의 특징이자 장점이라고 볼 수 있겠습니다. 

(asynchronous 처리를 지원하는 함수들을 써야 이런 식으로 동작합니다.)


리액트의 setState 함수 특징

function App(){
  let [name, setName] = useState('kim')
}

 

setName을 사용하시면 name이라는 state를 자유롭게 변경 가능합니다. 

근데 문제는 setName() 같은 state 변경함수들은 전부 asynchronous (비동기적) 으로 처리 됩니다.

그러니까 setName()이 오래걸리면 이거 제껴두고 다른 밑에 있는 코드들부터 실행한다는 겁니다.

그래서 뭔가 예상치 못한 문제가 생길 수 있습니다.


예제

function App(){
  let [count, setCount] = useState(0);
  let [age, setAge] = useState(20);

  return (
    <div>
      <div>{age}살 입니다.</div>
      <button>나이 추가</button>
    </div>
  )
}

 

위 코드를 참고해서 버튼을 누를 때마다 count라는 state를 +1 해야합니다. (버튼누른 횟수 기록용)

age라는 state도 +1 해야 합니다. 

근데 count가 3이상이면 더 이상 age라는 state를 1 더하지 말도록 코드를 짭니다.

버튼 3번 이상 누르면 (count가 3 이상이면) 나이를 그만더하라는 기능입니다. 그니까 22살에서 멈춰야합니다.


예제 코드

<button onClick={()=>{

  setCount(count+1);
  if ( count < 3 ) {
    setAge(age+1);
  }
         
}}>나이 추가</button>

 

버튼을 누르면 count를 +1 해줍니다. 그리고 count가 3보다 적으면 age를 +1 해줍니다.

그러면 아마 count라는게 2일 때까지 실행해주니까 age는 20에서 22가 되면 더이상 증가하지 않고 멈추겠군요. 

 

근데 23까지 증가합니다. 분명 count가 2일 때까지만 age를 +1 해주라고 했습니다.

count가 1일 때 age +1, count가 2일 때 age +1, count가 3이면 조건식을 나가버리니까요.

근데 지금은 count가 3일 때도 age +1를 해주고 있는 듯 합니다.


이유

async라는 특징 때문에 그렇습니다. state 변경함수는 async하게 처리되는 함수기 때문에 

완료되기까지 시간이 걸리면 제껴두고 다음 코드를 실행해줍니다.

 

그래서 코드를 해석해보자면 

버튼을 세번째 누르면 setCount(count+1); 이걸 실행해서 count를 3을 만들어줍니다.

근데 count를 3으로 만드는건 시간이 걸리니까 제껴두고 if ( count > 3 ) {} 이걸 실행합니다.

이 때 count는 아직 2라서 if문 안의 setAge(age+1)이 잘 동작하고 있는겁니다.

 

이 모든 문제는 setCount()가 async 함수라서 그렇습니다. 

async함수는 오래 걸리면 제껴두고 다음 줄 코드부터 실행하니까 그렇습니다.

 

그래서 저렇게 state1 변경하고나서 state2를 변경하는 코드를 작성할 땐 가끔 문제가 생깁니다.

이걸 정확히 sync스럽게, 순차적으로 실행하고 싶을 때 해결책은 useEffect입니다.


해결하는 방식

let [count, setCount] = useState(0);
    let [age, setAge] = useState(20);

    useEffect(() => {
        if(count != 0 && count < 3){
            setAge(age+1);
        }
    }, [count]);

    return (
    <div>
        <div>{age}살 입니다</div>
        <button onClick={() => {
            setCount(count+1);
        }}>나이 추가</button>
    </div>
    )

 

버튼 클릭시 count+1 해달라고 하고 useEffect의 dependency안에 count를 넣어 count가 업데이트 될 때

count < 3 이라면 age+1을 실행 해달라고 작성합니다.

이런식으로 해야 count라는 state가 변경이 완료되고 나서 나머지 코드가 실행이 되니까 제대로 동작합니다.

근데 문제는 처음 페이지 로드 시에도 useEffect 안의 코드가 실행되기 때문에 조건식에 count != 0을 추가 했습니다.