[JS] API ์์ฒญ / ๋น๋๊ธฐ ์์ ์ทจ์ํ๊ธฐ - Abort Controller
AbortController๋ 1๊ฐ ์ด์์ API ์์ฒญ์ ์ทจ์ํ ๋ ์ฌ์ฉํ๋ ์ธํฐํ์ด์ค๋ค. ์ฃผ๋ก ์ค๋ณต ์์ฒญ์ด ์์ ๋ ์ด์ ์์ฒญ์ ์ทจ์ํ ๋ ์ฌ์ฉํ๋ฉฐ, ๋น๋๊ธฐ ์์ ์ ๋ค๋ฃฐ ๋๋ ํ์ฉํ ์ ์๋ค. Axios 0.22 ๋ฒ์ ๋ถํฐ AbortController๋ฅผ ์ด์ฉํด์ API ์์ฒญ์ ์ทจ์ํ ์ ์๋ค. Cancel ํ ํฐ์ ์ด์ฉํ๋ ๋ฐฉ์์ deprecated ๋๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ ๋ฐฉ๋ฒ
AbortController๋ ์๋ 3๊ฐ์ง ๋จ๊ณ๋ก ์ฌ์ฉํ๋ค. abortController.abort()
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด abort ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉฐ fetch
ํ๋ก๋ฏธ์ค๋ rejected
์ํ๊ฐ ๋๊ณ ์ ์ด๋ catch
๋ธ๋ญ์ผ๋ก ์ง์
ํ๋ค.
AbortController
์ธ์คํด์ค ์์ฑ- ์ธ์คํด์ค์
signal
ํ๋กํผํฐ๋ฅผfetch
์signal
์ต์ ์ ํ ๋น — AbortSignal ์ธ์คํด์ค ๋ฑ๋ก abortController.abort
๋ฉ์๋ ํธ์ถํด์ ์์ฒญ ์ทจ์ — abort ์ด๋ฒคํธ ํธ์ถ
// ๊ธฐ๋ณธ ์ฌ์ฉ ๋ฐฉ๋ฒ
const abortController = new AbortController(); // โด AbortController ์ธ์คํด์ค ์์ฑ
// axios.get('...', { signal: abortController.signal })
fetch('https://random-data-api.com/api/v2/users', {
signal: abortController.signal, // โต AbortSignal ์ธ์คํด์ค ๋ฑ๋ก
})
.then(console.log) // ์์ฒญ์ ์ทจ์ํ์ผ๋ฏ๋ก ๊ฑด๋๋
.catch((e) => console.log(e.name)); // "AbortError"
abortController.abort('abort test'); // โถ abort ์ด๋ฒคํธ ํธ์ถ (API ์์ณฅ ์ทจ์)
์์ฒญ์ ์ทจ์ํ๋ฉด signal
ํ๋กํผํฐ์ aborted
์์ฑ์ด true
๋ก ๋ฐ๋์ด ์๋๊ฑธ ํ์ธํ ์ ์๋ค. reason
์์ฑ์ abort()
๋ฉ์๋๋ฅผ ํธ์ถํ์ ๋ ์ธ์๋ก ๋๊ธด ํ
์คํธ ๋ฉ์์ง๊ฐ ๋ด๊ฒจ์๋ค.
AbortController๋ก ์ธํ ์์ฒญ ์ทจ์ ํ๋ณ ๋ฐฉ๋ฒ
catch
๋ฉ์๋์์ Error ๊ฐ์ฒด์name
ํ๋กํผํฐ๊ฐ ์๋์ ๊ฐ์ ๋- fetch :
"AbortError"
- axios:
"CanceledError"
- fetch :
catch
๋ฉ์๋์์abortController.signal.aborted
๊ฐ์ดtrue
์ผ ๋- (axios)
axios.isCancel(event)
๋ฐํ๊ฐ์ดtrue
์ผ ๋
AbortSignal
AbortController.signal
ํ๋กํผํฐ๋ DOM ์์ฒญ๊ณผ ํต์ ํ๊ฑฐ๋ ์ทจ์ํ ๋ ์ฌ์ฉํ๋ AbortSignal
์ ์ธ์คํด์ค๋ค(์ฝ๊ธฐ ์ ์ฉ). signal
ํ๋กํผํฐ๋ abort()
๋ฉ์๋์ ํธ์ถ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ aborted
์์ฑ์ ๊ฐ์ง๋ค. abort ์ด๋ฒคํธ์ ๋ฆฌ์ค๋๋ฅผ ๋ฑ๋กํ ๋๋ signal
ํ๋กํผํฐ๋ฅผ ์ด์ฉํ๋ค.
const controller = new AbortController(); // AbortController ์ธ์คํด์ค ์์ฑ
const signal = controller.signal; // AbortSignal ์ธ์คํด์ค ๋ฐํ
// signal ๊ฐ์ฒด์ abort ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ๋ก
signal.addEventListener('abort', () => console.log('abort ์ด๋ฒคํธ ๋ฐ์!'));
controller.abort(); // abort ์ด๋ฒคํธ ๋ฐ์!
console.log(signal.aborted); // true
AbortController.signal
ํ๋กํผํฐ๋ฅผ ์ด์ฉํด ๋ค์ด๋ก๋ ์์
์ ์๋์ผ๋ก ์ทจ์ํ๋๋ก ํ ์๋ ์๋ค.
// ์ฝ๋ ์ฐธ๊ณ MDN
const controller = new AbortController();
const { signal } = controller;
const $downloadBtn = document.querySelector('.download'); // ๋ค์ด๋ก๋ ์์ ๋ฒํผ
const $abortBtn = document.querySelector('.abort'); // ๋ค์ด๋ก๋ ์ทจ์ ๋ฒํผ
const $reports = document.querySelector('.reports'); // ์๋ฌ ๋ฉ์์ง ํ์ ์์
$downloadBtn.addEventListener('click', fetchVideo); // ํด๋ฆญ์ fetchVideo ์์ฒญ ์์
$abortBtn.addEventListener('click', () => {
controller.abort(); // fetchVideo ์์ฒญ ์ทจ์
console.log('Download aborted');
});
function fetchVideo() {
fetch('url', { signal })
.then((response) => {
// ...
})
.catch((e) => {
$reports.textContent = 'Download error: ' + e.message;
});
}
์ ์์ ์ฝ๋์ ์ํ ํ์ด์ง via MDN โผ
์ฌ๋ฌ ์์ฒญ ์ทจ์ํ๊ธฐ
์๋์ฒ๋ผ ์ฌ๋ฌ fetch ์์ ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ๋ ์ํฉ์์ 1๊ฐ์ AbortController๋ฅผ ์ด์ฉํด ๋ชจ๋ ์์ฒญ์ ์ทจ์ํ ์ ์๋ค. ์ด์ฒ๋ผ AbortController๋ ํ์ฅ ๊ฐ๋ฅํ(scalable) ํน์ง์ ๊ฐ์ง๊ณ ์๋ค.
// ์ฝ๋ ์ฐธ๊ณ JavaScript Info
const urls = ['...']; // a list of urls to fetch in parallel
const controller = new AbortController();
// an array of fetch promises
const fetchJobs = urls.map((url) =>
fetch(url, {
signal: controller.signal,
}),
);
const results = await Promise.all(fetchJobs);
// if controller.abort() is called from elsewhere,
// it aborts all fetches
fetch ์์ฒญ๊ณผ ๋ค๋ฅธ ๋น๋๊ธฐ ์์ ์ด ํจ๊ป ์์ ๋๋ 1๊ฐ์ AbortController๋ฅผ ์ฌ์ฉํด ๋ชจ๋ ์์ฒญ/์์ ์ ์ทจ์ํ ์ ์๋ค.
// ์ฝ๋ ์ฐธ๊ณ JavaScript Info
const urls = ['...'];
const controller = new AbortController();
const asyncJobs = new Promise((resolve, reject) => {
// ...
controller.signal.addEventListener('abort', reject);
});
const fetchJobs = urls.map((url) =>
fetch(url, {
signal: controller.signal,
}),
);
// Wait for fetches and our task in parallel
let results = await Promise.all([...fetchJobs, asyncJobs]);
// if controller.abort() is called from elsewhere,
// it aborts all fetches and asyncJobs
React์์ ์ฌ์ฉ ๋ฐฉ๋ฒ
๐ก ๊ธฐ๋ณธ์ ์ผ๋ก Cancel ํ ํฐ์ ์ฌ์ฉํ์ ๋ ๋ฐฉ๋ฒ๊ณผ ๋์ผํ๋ค.
useEffect
์ ์ฒซ๋ฒ์งธ ์ธ์ effect ํจ์์ return
๋ฌธ์ ๋ช
์ํ๋ฉด ํด๋น ํจ์๋ฅผ ํด๋ฆฐ์
ํจ์๋ก ์ธ์ํ๋ค. ํด๋ฆฐ์
ํจ์๋ โ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ํน์ โ๋งค ๋ ๋๋ง ํ ๋ค์ effect ํจ์๋ฅผ ํธ์ถํ๊ธฐ ์ ์ ์คํ๋๋ค.
ํด๋ฆฐ์ ํจ์ ์์ ์๋ ๊ฐ์ ์ ๋ฐ์ดํธ ์ด์ ์ ๊ฐ์ ์ฐธ์กฐํ๋ฏ๋ก, ํด๋ฆฐ์ ํจ์์ ์๋ ์ทจ์ ํ ํฐ์ ์ด์ Axios ์์ฒญ์ ๋ํ ํ ํฐ์ด๋ค. ์ฆ, A ์์ฒญ์ด ์งํ์ค์ผ ๋ B๋ฅผ ์์ฒญํ๋ฉด A ์์ฒญ์ด ์ทจ์๋๋ค. ๋๋ต ์๋ ๊ฐ์ ๊ตฌ์กฐ๋ค.
- ์์ฒญ
A
์์ — effect(signal 1) - ์์ฒญ
A
๋์ค ์๋ก์ด ์์ฒญB
๋ฐ์ - ์์ฒญ
A
์ทจ์ — cleanup(signal 1) - ์์ฒญ
B
์์ — effect(signal 2)
const Component = () => {
useEffect(() => {
const abortController = new AbortController(); // ์ธ์คํด์ค ์์ฑ
axios
.get('url', { signal: abortController.signal }) // abort ์ด๋ฒคํธ์ ๋ฆฌ์ค๋ ๋ฑ๋ก
.then(({ data }) => {
// ์์ฒญ ์ฑ๊ณต...
})
.catch((e) => {
if (abortController.signal.aborted) return; // ์์ฒญ ์ทจ์ ์ํด ๋ฐ์ํ ์๋ฌ
// ์ค๋ฅ ์ฒ๋ฆฌ...
});
return () => abortController.abort(); // ์๋ก์ด ์์ฒญ ์๊ธฐ๋ฉด ์ด์ ์์ฒญ ์ทจ์
}, []);
// ...
};
Promise ์ทจ์ํ๊ธฐ
AbortController๋ API ์์ฒญ ์ธ์๋ ๋น๋๊ธฐ ์์ ์ ์ทจ์ํ ๋๋ ํ์ฉํ ์ ์๋ค.
์๋ ์์ ์์ wait
ํจ์๋ ์ธ์๋ก ๋ฐ์ time
(๋๊ธฐ ์๊ฐ) ์ดํ resolve
ํจ์๋ฅผ ํธ์ถํด์ ํ๋ก๋ฏธ์ค๋ ์ดํ ์ํ๊ฐ ๋๋ค. ๋๋ฒ์งธ ์ต์
์ธ์๋ก ๋ฐ๋ signal
ํ๋กํผํฐ๋ abort ์ด๋ฒคํธ ๋ฆฌ์ค๋์ ๋ฑ๋กํด๋๋ค.
๋ง์ฝ time
๋๊ธฐ ์๊ฐ ์ ์ abortController.abort()
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด abort ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ์คํ๋ผ์ ํ์ด๋จธ๋ฅผ ํด์ ํ๊ณ ํ๋ก๋ฏธ์ค๋ ์คํจ ์ํ๊ฐ ๋๋ค. ๊ทธ ํ ์ ์ด๋ catch
๋ฌธ์ผ๋ก ๋์ด๊ฐ์ ์ฝ์์ ์ถ๋ ฅํ๋ค.
// ์ฝ๋ ์ฐธ๊ณ Wanago
function wait(time: number, signal?: AbortSignal) {
return new Promise<string>((resolve, reject) => {
const timeoutId = setTimeout(() => {
resolve(`${time} seconds passed`);
}, time);
signal?.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject('Waiting was interrupted');
});
});
}
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, 1000);
wait(5000, abortController.signal)
.then(console.log)
.catch(console.error); // Error! 'Waiting was interrupted'
๋ ํผ๋ฐ์ค
- ์๋ฐ์คํฌ๋ฆฝํธ์์ AbortController ๋ฅผ ํ์ฉํ์ฌ ๋น๋๊ธฐ ์์ ์ค๋จํ๊ธฐ
- Using React to understand Abort Controllers
- Fetch: Abort
- AbortController.signal - Web API | MDN
- Using AbortController to deal with race conditions in React
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[JS] ํ์ ์ด๋ฆ์ ๋ฐํํ๋ getType ์ ํธ ํจ์
[JS] ํ์ ์ด๋ฆ์ ๋ฐํํ๋ getType ์ ํธ ํจ์
2024.05.14 -
[Next.js] ๋ผ์ฐํธ ๋ณ๊ฒฝ / ์๋ก๊ณ ์นจ ์ทจ์ํ๊ธฐ (๋ค๋น๊ฒ์ด์ ๊ฐ๋)
[Next.js] ๋ผ์ฐํธ ๋ณ๊ฒฝ / ์๋ก๊ณ ์นจ ์ทจ์ํ๊ธฐ (๋ค๋น๊ฒ์ด์ ๊ฐ๋)
2024.05.14 -
[Next.js] Next/Image base64 placeholder ๋ง๋ค๊ธฐ (๋ธ๋ฌ ์ฒ๋ฆฌ๋ ํ๋ ์ด์คํ๋)
[Next.js] Next/Image base64 placeholder ๋ง๋ค๊ธฐ (๋ธ๋ฌ ์ฒ๋ฆฌ๋ ํ๋ ์ด์คํ๋)
2024.05.14 -
[DevTools] Tailwind CSS ์ ํธ๋ฆฌํฐ ํด๋์ค ์๋ ์ ๋ ฌ ํ๋ฌ๊ทธ์ธ
[DevTools] Tailwind CSS ์ ํธ๋ฆฌํฐ ํด๋์ค ์๋ ์ ๋ ฌ ํ๋ฌ๊ทธ์ธ
2024.05.13