React 中的 异步数据

3,125 阅读2分钟

Promise

在应用中,有的时候我们必须先渲染出一个组件,然后再请求第三方 API 得到数据并将其显示在组件上。接下来我们将在应用中模拟这种异步数据过程。在真实应用里,异步数据是从真正的远端 API 中获取的。我们从一个简单返回 promise 的函数出发,一直到数据被解析完成结束。被解析的对象数据包括了之前的 stories 列表数据:

const initialStories = [ ... ];
const getAsyncStories = () => Promise.resolve({ data: { stories: initialStories } });

APP 组件中,使用一个空数组作为初始的 state,而不是使用 initialStories。我们希望从一个空的 stories 列表开始,并模拟异步获取这些 stories 的数据的过程。在新的 useEffect hook 中,调用方法并返回被解析的 promise。由于 useEffect 的依赖数组是个空数组,因此管理副作用的函数仅在组件首次渲染后运行:

const App = () => {
    ...
    const [stories, setStories] = React.useState([]);
    React.useEffect(() => {
        getAsyncStories().then(result => {
            setStories(result.data.stories);
        });
    }, []);
    ...
};

虽然我们启动应用的时候数据并非同时到达,但由于它是立刻渲染的,使数据看起来似乎是同步到达的。因为每个对 API 的网络请求都会带来延迟,所以现在让我们给它加上一个真实的延迟。首先,删除之前的简写版本:

const getAsyncStories = () =>
    new Promise(resolve =>
    resolve({ data: { stories: initialStories } })
);

其次在解析 promise 时,将其延迟几秒钟:

const getAsyncStories = () =>
    new Promise(resolve =>
    setTimeout(() => resolve({ data: { stories: initialStories } }),2000)
);

再次启动应用之后,你应该能看到列表数据会延迟几秒后再渲染出来。而由于初始的stories 是个空数组,在 APP 组件渲染之后副作用 hook 会运行一次以获取异步数据。在解 析promise以及将数据设置为组件的 state 之后,组件会再次渲染并显示异步加载的 stories列表。

Async / Await

React 里经常需要处理异步数据,所以了解 promise 的另外一种语法形式是大有裨益的,也就是:async / await。下面这段对 handleFetchStories 的重构展示了如何使用它们,不包含错误处理:

const App = () => {
    ...
    const handleFetchStories = React.useCallback(async () => {
        dispatchStories({ type: 'STORIES_FETCH_INIT' });
        const result = await axios.get(url);
        dispatchStories({
            type: 'STORIES_FETCH_SUCCESS',
            payload: result.data.hits,
        });
    }, [url]);
    ...
};

想使用 async / await,需要给函数加 async 关键字。一旦开始使用 await 关键字,代码读起来就像同步的了。await 关键字后面的动作会等到 promise 正常返回后才会执行,也就是说代码会等待:

const App = () => {
    ...
    const handleFetchStories = React.useCallback(async () => {
        dispatchStories({ type: 'STORIES_FETCH_INIT' });
        try {
            const result = await axios.get(url);
            dispatchStories({
                type: 'STORIES_FETCH_SUCCESS',
                payload: result.data.hits,
            });
        } catch {
            dispatchStories({ type: 'STORIES_FETCH_FAILURE' });
        }
    }, [url]);
    ...
};

如果需要和以前一样加入错误处理,则要用到 try / catch 语句。如果 try 里面出错了的话,代码会跳到 catch 里做错误处理。then / catch 语法和async / await里的 try / catch 语法在JavaScriptReact 里处理异步数据是同样有效的。