Async Code in useEffect is Dangerous. How Do We Deal with It? - This Dot Labs
The introduction of async/await to Javascript has made it easy to express complex workflows that string together multiple asynchronous tasks. Let's take a look…
www.thisdot.co
Javascript에 async/await가 도입되면서 여러 비동기 작업을 함께 묶는 복잡한 워크플로를 쉽게 표현할 수 있습니다.
const useClient = (user) => {
const [client, setClient] = useState(null);
useEffect(() => {
(async () => {
const clientAuthToken = await fetchClientToken(user);
const connection = await createWebsocketConnection();
const client = await createClient(connection, clientAuthToken);
setClient(client);
})();
}, [user]);
useEffect(() => {
return () => {
client.disconnect();
};
}, [client]);
return client;
};
위 코드를 보고 보고 모든 것이 장밋빛이라고 생각하기 쉽습니다.
user를 전달했을 때 클라이언트를 생성하고,
user 변경이나 컴포넌트 언마운트를 통해 클라이언트가 폐기될 때마다 클라이언트의 연결을 끊습니다.
그러나 첫 번째 useEffect의 비동기 워크플로가 다른 Effect및 사용자 작업에 독립적으로 응답하는 다른 응용 프로그램과 동시에 실행되고 있다는 점은 고려하지 않았습니다.
const useClient = (user) => {
// no state
useEffect(() => {
let client;
(async () => {
const clientAuthToken = await fetchClientToken(user);
const connection = await createWebsocketConnection();
client = await createClient(connection, clientAuthToken);
})();
return () => {
client?.disconnect(); // here
};
}, [user]);
return client;
};
이제 클라이언트가 생성되면 컴포넌트 상태에 저장하지 않습니다.
하지만 여전히 연결 끊기는 컴포넌트가 언마운트 되기 전에 Promise가 resolve되어야 동작합니다.
const useClient = (user) => {
const [client, setClient] = useState(null);
useEffect(() => {
let cancelled;
(async () => {
const clientAuthToken = await fetchClientToken(user); // resolve
// if cancelled before we get to creating resources
// it's ok, just don't create them
// then
if (cancelled) return;
const connection = await createWebsocketConnection(); // resolve
// if cancelled before we get to the client, we need
// to make sure our connection isn't left hanging
// then
if (cancelled) {
connection.close();
return;
}
const client = await createClient(connection, clientAuthToken); // resolve
// if cancelled after the client has been created, we
// need to clean it up
// then
if (cancelled) {
client.disconnect();
return;
}
setClient(client);
})();
return () => {
cancelled = true;
};
}, [user]);
useEffect(() => {
return () => {
client.disconnect();
};
}, [client]);
return client;
};
취소 핸들러를 어디에 둘 것인지 이해하는 데 어려움을 겪고 있다면,
async / await 대신 Promise를 사용하여 이 코드를 작성한다고 상상해 보십시오.
모든 .then 콜백 시작 시 취소를 처리해야 합니다.
const useClient = (user) => {
const [client, setClient] = useState(null);
useEffect(() => {
let cancelled;
fetchClientToken(user).then((clientAuthToken) => {
if (cancelled) return;
createWebsocketConnection().then((connection) => {
if (cancelled) {
connection.close();
return;
}
createClient(connection, clientAuthToken).then((client) => {
if (cancelled) {
client.disconnect();
return;
}
setClient(client);
});
});
});
return () => {
cancelled = true;
};
}, [user]);
useEffect(() => {
return () => {
client.disconnect();
};
}, [client]);
return client;
};
XState 구현은 2부에서...
'FrontEnd' 카테고리의 다른 글
XState 공식 문서 번역 : 서비스 호출(Invoking Services) (0) | 2022.05.01 |
---|---|
XState와 비동기 2편: XState Actor와 함께 비동기를 안전하게 모델링 (0) | 2022.05.01 |
XState : 상태 머신과 상태차트 소개 (0) | 2022.04.30 |
[TypeOrm]ORM을 프로젝트에 도입할 때 주의할점 (1) | 2022.04.30 |
XState : 액터 모델 간단히 알아보기 (0) | 2022.04.30 |