일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Next
- 자바스크립트
- 타입스크립트
- 제로베이스
- 알고리즘문제풀이
- NPM
- 자바스크립트 문제
- 자바스크립트 문제 풀이
- 리액트쿼리
- leetcode
- Baekjoon
- HTML
- 자바스크립트 연결리스트
- 리액트
- 자바스크립트 문제풀이
- stack문제
- JS
- til
- JavaScript
- 프로그래머스
- 자바스크립트 알고리즘 문제
- 자바스크립트 알고리즘
- react
- lodash
- 프론트엔드
- CSS
- leetcode문제풀이
- Next.js13
- next13
- 자바스크립트코딩테스트
- Today
- Total
코드노트
Race Condition 정리 및 해결방법 본문
Race Condition?
- 두 개 이상의 작업이 동시에 실행되거나 예상치 못한 순서로 완료될 때 발생하는 문제점이다.
리액트를 사용하면서 한번쯤은 만나본 문제이기도 하다. 이로 인해서 데이터 상태가 예측할 수 없는 상태로 변경될 수 있다.
특히 비동기 코드에서는 필수적으로 생각하고 코드를 작성해야한다!
그럼 Race Condition는 왜 일어나며 어떤 문제점을 가지고 있을까?
- 여러 비동기 작업이 동시에 실행되면서 서로 경쟁하는 상황이 발생할 수 있다. 비동기 작업이 순차적으로 이뤄지지 않고 병렬적으로 실행되기 때문에 동시에 업데이트가 된다면 예상치 못한 상태 변경이 발생할 수 있다.
- 리엑트를 사용할 때 컴포넌트가 비동기적으로 데이터를 가져오고 동시에 setState를 통해 상태를 업데이트를 할때 주로 발생한다.
- 어? 나는 이런적이 없는데? 아니 코드를 100번, 1,000번 실행했을 때 실행 결과가 같아야하지 않을까? 단 한번이라도 Race Condition가 발생한다면 그건 꼭 해결해야하는 문제이다.
- react 공식 dev에서도 이야기를 하고 있는것을 볼 수 있다. 참고!
React에서의 Race Condition 예시
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(`https://api.example.com/user/${userId}`);
const data = await response.json();
setUserData(data);
}
fetchData();
}, [userId]);
return (
<div>
{userData ? <h1>{userData.name}</h1> : <p>Loading...</p>}
</div>
);
}
- 이 코드는 userId를 props로 받아서 userId가 변경될때마다 fetch로 api요청 후 data를 setUserData로 상태를 변경시켜준다.
- 여기서 문제 userId가 계속해서 바뀌고 이전 요청이 나중에 도착하여 잘못된 userData로 변경될 수 있다.
- 이 문제는 then을 사용해도되지 않나요? 라고도 하지만 계속해서 userId가 바뀐다면 비동기로 진행된 fetch는 언제 data를 가져와서 어떤 데이터가 set함수에 적용될지 완벽하게 예상할 수 없다.
Race Condition 해결 방법
1. Cleanup 함수 사용 (useEffect)
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
let isMounted = true;
async function fetchData() {
const response = await fetch(`https://api.example.com/user/${userId}`);
const data = await response.json();
if (isMounted) {
setUserData(data);
}
}
fetchData();
return () => {
isMounted = false;
};
}, [userId]);
return (
<div>
{userData ? <h1>{userData.name}</h1> : <p>Loading...</p>}
</div>
);
}
- isMounted 변수를 통해서 컴포넌트가 마운트되어있는지 확인한다.
- fetchData함수로 비동기 작업이 완료된 후 isMounted가 true일때만 set함수로 userData를 업데이트 한다.
- 이후 isMounted를 false로 처리하여 컴포넌트가 언마운트 상태에서는 set함수를 호출하지 않게하여 race condition로 인한 불필요한 상태 업데이트를 방지할 수 있다.
2. AbortController 사용
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
async function fetchData() {
try {
const response = await fetch(`https://api.example.com/user/${userId}`, { signal });
const data = await response.json();
setUserData(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
}
}
fetchData();
return () => {
controller.abort();
};
}, [userId]);
return (
<div>
{userData ? <h1>{userData.name}</h1> : <p>Loading...</p>}
</div>
);
}
- AbortController는 비동기 요청을 취소시킬 수 있다. 네트워크 요청을 명시적으로 중단시켜 Race Condition를 방지할 수 있다.
- signal 객체는 fetch 요청에 전달되고 요청이 중단될 때 이 신호를 감지할 수 있다. fetch 함수에 signal을 전달하여 요청이 중단될 수 있도록 설정한다. 요청이 만약 성곡적으로 완료되면 set함수에 상태를 업데이트하고 요청이 중단되면 따로 에러 처리!
- cleanup함수는 컴포넌트가 언마운트 되거나 userId가 변경될때 호출 되는데 이때 controller.abort를 호출하여 현재 진행중인 요청을 취소할 수 있다.
- 만약 응답이 도착하기 전에 컴포넌트가 언마운트 되거나 userId가 변경된 이후에도 비동기 작업이 상태를 업데이트 하지 않도록 한다.
- AbortController를 사용하여 안전하게 관리할 수 있으며, isMounted와 같이 따로 변수를 통해 플래그를 사용하지 않고도 race condition를 해결할 수 있다.
3. Suspense 사용
import React, { Suspense } from 'react';
// 비동기 데이터를 처리하는 함수
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Data loaded');
}, 2000);
});
}
// 데이터를 감싸는 함수
function wrapPromise(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
const resource = wrapPromise(fetchData());
// 비동기 데이터를 표시하는 컴포넌트
function DataComponent() {
const data = resource.read();
return <div>{data}</div>;
}
// 앱 컴포넌트
export default function App() {
return (
<div>
<h1>Suspense Example</h1>
<Suspense fallback={<p>Loading...</p>}>
<DataComponent />
</Suspense>
</div>
);
}
- 비동기 요청이 완료되기 전에 컴포넌트는 로딩 상태를 표시한다.
- 데이터를 받기 전까지 fallback UI를 보여주며 데이터를 기다린다.
- 데이터가 성공적으로 로드되면 로딩 상태를 종료하고 데이터가 준비된 상태로 컴포넌트를 렌더링 한다.
- Suspense는 Concurrent Mode와 함께 사용되며, 여러 비동기 작업이 동시에 실행되더라도 상태를 일관되게 유지 한다. 데이터가 완료되기 전까지는 로딩 상태를 보여주고 데이터가 준비되면 그때 최신상태로 업데이트 한다.
- 데이터 요청이 중복되거나 Race Condition이 발생한다면, 마지막으로 완료된 요청의 결과만 실제로 적용된다.
- Suspense가 최신 상태의 데이터를 항상 렌더링하기 때문에 자연스럽게 해결된다.
'Code note > Error문제해결' 카테고리의 다른 글
Unable to resolve "missing-asset-registry-path" from 문제 해결 (0) | 2024.03.22 |
---|---|
Next.js SSG에서 SSR로 변환, Cache-Control 별도로 지정하기 (0) | 2023.08.18 |
Next.js 13 SSR에서 현재경로 가져오기 실패 기록 (0) | 2023.07.10 |
리액트쿼리 Provider 에러핸들링 정리 (0) | 2023.06.24 |
브랜치 CONFLICT 해결방법, pull? fetch? (1) | 2023.02.08 |