目錄
基本概念
工作原理
優點
注意事項
底層原理
實際應用場景
1. 數據獲取 (API 請求)
2. 表單提交
3. 異步狀態管理
4. 異步路由切換
5. 異步數據預加載
6. 第三方 API 調用
7. 文件上傳/下載
8. 路由導航攔截
關鍵注意事項
基本概念
async
函數:用 async
關鍵字聲明的函數會返回一個 Promise
。如果函數中返回的不是 Promise
,JavaScript 會將其包裝成一個 resolved 的 Promise
。
await
關鍵字:只能用在 async
函數內部,用于等待一個 Promise
的解決結果。如果等待的不是 Promise
,await
會直接返回該值。
示例代碼
// 定義一個異步函數
async function fetchData() {try {// 模擬一個異步操作,比如網絡請求const response = await fetch('https://api.example.com/data');const data = await response.json(); // 解析響應體為 JSONconsole.log(data);} catch (error) {console.error('Error fetching data:', error);}
}// 調用異步函數
fetchData();
工作原理
async
函數返回一個 Promise
:
如果函數中返回的是一個普通值,這個值會被包裝成一個 resolved 的 Promise
。
如果函數中返回的是一個 Promise
,則直接返回該 Promise
。
await
暫停函數執行:
當遇到 await
時,函數的執行會被暫停,直到等待的 Promise
被解決。
如果等待的不是一個 Promise
,await
會直接返回該值,并繼續執行后續代碼。
優點
更簡潔的代碼:相比傳統的 Promise
鏈式調用,async/await
讓異步代碼看起來像同步代碼,邏輯更清晰。
易于調試:錯誤堆棧更清晰,調試時更容易追蹤代碼的執行流程。
避免回調地獄:通過 await
逐層等待,避免了嵌套回調的復雜性。
注意事項
只能在 async
函數中使用 await
:
// 錯誤:await 不能在普通函數中使用
function normalFunction() {const result = await somePromise(); // 語法錯誤
}
錯誤處理:
async function fetchData() {try {const response = await fetch('https://api.example.com/data');const data = await response.json();console.log(data);} catch (error) {console.error('Error:', error);}
}
使用 try/catch
塊捕獲 await
的錯誤,而不是 .catch()
。
并發執行: 如果有多個異步操作需要同時執行,可以使用 Promise.all()
:
async function fetchMultipleData() {const [data1, data2] = await Promise.all([fetch('https://api.example.com/data1').then(res => res.json()),fetch('https://api.example.com/data2').then(res => res.json())]);console.log(data1, data2);
}
底層原理
async/await
的底層是基于 Promise
和事件循環的。當執行一個 async
函數時,函數內部的代碼會被包裝成一個 Promise
,并立即執行。遇到 await
時,當前函數的執行會被暫停,直到等待的 Promise
被解決。解決后,函數會從暫停的地方繼續執行。
實際應用場景
1. 數據獲取 (API 請求)
從服務器獲取數據是最常見的異步操作場景。
import React, { useState, useEffect } from 'react';const DataFetcher = () => {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error('Network response was not ok');}const jsonData = await response.json();setData(jsonData);} catch (error) {setError(error.message);} finally {setLoading(false);}};fetchData();}, []);if (loading) return <div>Loading...</div>;if (error) return <div>Error: {error}</div>;return <div>{JSON.stringify(data)}</div>;
};export default DataFetcher;
2. 表單提交
處理表單提交時,使用 async/await
等待服務器響應。
import React, { useState } from 'react';const AsyncForm = () => {const [name, setName] = useState('');const [email, setEmail] = useState('');const [status, setStatus] = useState('idle');const handleSubmit = async (e) => {e.preventDefault();setStatus('submitting');try {const response = await fetch('https://api.example.com/submit', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ name, email }),});if (!response.ok) {throw new Error('Failed to submit');}setStatus('success');} catch (error) {setStatus(`Error: ${error.message}`);}};return (<form onSubmit={handleSubmit}><inputtype="text"value={name}onChange={(e) => setName(e.target.value)}placeholder="Name"/><inputtype="email"value={email}onChange={(e) => setEmail(e.target.value)}placeholder="Email"/><button type="submit" disabled={status === 'submitting'}>{status === 'submitting' ? 'Submitting...' : 'Submit'}</button>{status === 'success' && <div>Submitted successfully!</div>}{status.startsWith('Error') && <div>{status}</div>}</form>);
};export default AsyncForm;
3. 異步狀態管理
在組件中管理異步狀態。
import React, { useState } from 'react';const AsyncStateExample = () => {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error('Network response was not ok');}const jsonData = await response.json();setData(jsonData);} catch (error) {console.error('Error fetching data:', error);} finally {setLoading(false);}};return (<div><button onClick={fetchData} disabled={loading}>{loading ? 'Loading...' : 'Fetch Data'}</button>{data && <div>{JSON.stringify(data)}</div>}</div>);
};export default AsyncStateExample;
4. 異步路由切換
在使用 React Router 時,完成異步操作后再切換路由。
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';const AsyncRouteExample = () => {const [username, setUsername] = useState('');const history = useHistory();const handleLogin = async () => {try {await new Promise((resolve) => setTimeout(resolve, 1000));history.push('/dashboard');} catch (error) {console.error('Login failed:', error);}};return (<div><inputtype="text"value={username}onChange={(e) => setUsername(e.target.value)}placeholder="Username"/><button onClick={handleLogin}>Login</button></div>);
};export default AsyncRouteExample;
5. 異步數據預加載
在組件渲染前預加載數據。
import React, { useState, useEffect } from 'react';const AsyncDataPreload = () => {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);useEffect(() => {const preloadData = async () => {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error('Network response was not ok');}const jsonData = await response.json();setData(jsonData);} catch (error) {console.error('Error preloading data:', error);} finally {setLoading(false);}};preloadData();}, []);if (loading) return <div>Loading data...</div>;return <div>{JSON.stringify(data)}</div>;
};export default AsyncDataPreload;
6. 第三方 API 調用
與瀏覽器 API 或第三方 SDK 交互。
async function getGeolocation() {return new Promise((resolve, reject) => {navigator.geolocation.getCurrentPosition(resolve, reject);});
}function LocationButton() {const [position, setPosition] = useState(null);const handleClick = async () => {try {const pos = await getGeolocation();setPosition({lat: pos.coords.latitude,lng: pos.coords.longitude});} catch (err) {alert('無法獲取位置信息');}};return (<div><button onClick={handleClick}>獲取當前位置</button>{position && <Map coordinates={position} />}</div>);
}
7. 文件上傳/下載
處理文件上傳并跟蹤進度。
import axios from 'axios';function FileUploader() {const [progress, setProgress] = useState(0);const uploadFile = async (file) => {const formData = new FormData();formData.append('file', file);await axios.post('/api/upload', formData, {onUploadProgress: (e) => {setProgress(Math.round((e.loaded * 100) / e.total));}});};return (<div><input type="file" onChange={e => uploadFile(e.target.files[0])} />{progress > 0 && <ProgressBar value={progress} />}</div>);
}
8. 路由導航攔截
防止用戶在有未保存更改時離開頁面。
import { useNavigate, useLocation } from 'react-router-dom';function EditArticle() {const [isDirty, setIsDirty] = useState(false);const navigate = useNavigate();useEffect(() => {const unblock = navigate.block((tx) => {if (isDirty && !window.confirm('有未保存的更改,確定離開?')) {tx.retry();}});return () => unblock();}, [isDirty]);const saveArticle = async () => {await fetch('/api/articles', { method: 'PUT' });setIsDirty(false);navigate('/articles');};return (/* 編輯器 UI */);
}
關鍵注意事項
-
組件卸載時的處理 在
useEffect
中使用標志位避免組件卸載后的狀態更新。
useEffect(() => {let isMounted = true;const fetchData = async () => {const data = await fetch('/api/data');if (isMounted) setData(data);};fetchData();return () => { isMounted = false; };
}, []);
-
錯誤處理 捕獲并處理異步操作中的錯誤,避免未處理的承諾拒絕。
try {await fetchData();
} catch (error) {if (error.name !== 'AbortError') {showErrorToast(error.message);}
}
-
防抖優化 對頻繁觸發的異步操作使用防抖。
const search = useDebouncedCallback(async (query) => {const results = await fetchResults(query);setResults(results);
}, 500);
通過以上示例和注意事項,可以全面了解在 React 中如何使用 async/await
處理各種異步場景,提升應用的響應性和用戶體驗。
React 中?async/await
?的典型應用場景包括:
-
網絡請求(GET/POST/PUT/DELETE)
-
文件操作(上傳/下載)
-
瀏覽器 API 調用(地理位置/攝像頭)
-
定時任務(延遲執行)
-
WebSocket 通信
-
數據庫操作(IndexedDB)
-
動畫序列控制
-
第三方庫集成(支付 SDK)
合理使用異步操作可以使:
-
代碼邏輯更清晰(避免回調地獄)
-
錯誤處理更直觀(try/catch 統一捕獲)
-
用戶體驗更友好(加載狀態/進度提示)
碼字不易,各位大佬點點贊唄