createAsyncThunk
概览
一个函数,其接受一个 Redux action type 字符串和一个应当返回 promise 对象的回调函数。根据传入的 action type 的前缀,它会生成关于 promise 生命周期的 action types,并且返回一个会运行 promise 回调函数、且根据返回的 promise 派发生命周期 actions 的 thunk action creator。
这么做把处理异步请求生命周期的标准推荐做法抽象出来了。
该函数不会生成任何 reducer 函数,因为它并不知道它接受的数据是什么、如何追踪加载状态、或者返回的数据需要被如何处理。你应当编写你自己的 reducer 逻辑来处理这些 actions,不管是用何种适合你应用的加载状态和处理逻辑。
示例用法:
参数
createAsyncThunk
接收三个参数:一个字符串 action type
值、一个 payloadCreator
回调,和一个 options
对象。
type
一个用于生成额外 Redux action type 常数、代表着一个异步请求生命周期的字符串。
例如,'users/requestStatus'
的 type
参数会生成如下的 action types:
pending
:'users/requestStatus/pending'
fulfilled
:'users/requestStatus/fulfilled'
rejected
:'users/requestStatus/rejected'
payloadCreator
一个回调函数,起应当返回一个包含异步逻辑处理结果的 promise 对象。它也有可能同步地返回一个值。如果产生了错误,它要么应当返回一个被 rejected 的带有 Error
实例的 promise,要么返回一个比如像描述错误的信息的普通值,亦或者返回一个 resolved 的 promise,其带有一个 RejectWithValue
参数,如 thunkAPI.rejectWithValue
的返回值一样。
payloadCreator
可以包含任何需要计算一个合适结果的逻辑。这包括一个标准的 AJAX 数据请求、多个会把结果结合到一起的 AJAX 调用、与 React Native 的 AsyncStorage
进行互动等等。
payloadCreator
有两个参数:
arg
: 一个单值,当派发 thunk action creator 时,该值包含传入到 payloadCreator 的第一个参数。对于传入像 ID 这样会被用作请求的一部分来的操作来说,是很有用的。如果你需要传入多个值,当你派发 thunk 的时候,把它们作为一个对象整体传入,例如dispatch(fetchUsers({status: 'active', sortBy: 'name'}))
。thunkAPI
: 一个包含所有通常会被传入到 a Redux thunk 函数的参数的对象,其还有其他的选项:dispatch
: Redux store 的dispatch
方法getState
: Redux store 的getState
方法extra
: 被传入到设置阶段 thunk 中间件的 ”额外参数“,如果有的话requestId
: 一个被自动生成去识别请求顺序的唯一字符串 ID 值signal
: 一个AbortController.signal
对象 ,其有可能被用于检查是否某部分应用逻辑已经把该请求标记为需要取消rejectWithValue
: rejectWithValue 是一个可以在你的 action creatorreturn
以返回一个带有定义好 payload 的 rejected 响应的工具函数。它会把任何你给它的值进行传递,并且在 rejected 的 action payload 返回该值。
任何上述表示的值,都可以在有需要的时候被用到 payloadCreator
的逻辑中,进行结果的计算。
选项
一个带有如下可选字段的逻辑:
condition
: 一个可以用于跳过 payload creator 执行和所有 action 派发的回调。查看Canceling Before Execution 获取完整的描述。dispatchConditionRejection
: 如果condition()
返回false
,默认的行为是没有任何的 actions 会被派发。如果当 thunk 被取消时,你仍然想派发一个 "rejected" action 的话,把这个值设置成true
。
返回值
createAsyncThunk
返回一个标准的 Redux thunk action creator。这个 thunk action creator 函数包含有处理 pending
, fulfilled
以及 rejected
情况的普通 action creators,作为其嵌套字段。
把上面例子中的 fetchUserById
当作例子,createAsyncThunk
会生成四个函数:
fetchUserById
,启动你编写的异步 payload 回调函数的 thunk action creator:fetchUserById.pending
,一个派发'users/fetchByIdStatus/pending'
action 的 action creatorfetchUserById.fulfilled
,一个派发'users/fetchByIdStatus/fulfilled'
action 的 action creatorfetchUserById.rejected
,一个派发'fetchByIdStatus/rejected'
action 的 action creator
当被派发时,thunk 会:
- 派发
pending
action - 调用
payloadCreator
回调函数和等待返回的 promise 结果 - 当 promise 有结果时:
- 如果 promise 成功了, 则带着该 promise 值作为
action.payload
派发fulfilled
action - 如果 promise 是带着
rejectWithValue(value)
的返回值被 resolved,则带着该传入到action.payload
的值派发rejected
action,并且 'Rejected' 作为action.error.message
- 如果 promise 失败了并且没有被
rejectWithValue
处理,则带着一个作为action.error
的序列化版本的错误值,派发rejected
action
- 如果 promise 成功了, 则带着该 promise 值作为
- 返回一个 fulfilled 的 promise 对象,包含最终被派发的 action (
fulfilled
或者rejected
action 对象)
Promise 生命周期 Actions
createAsyncThunk
会使用 createAction
生成三个 Redux action creators:pending
, fulfilled
和 rejected
。每一个生命周期 action creator 会被附加到返回的 thunk action creator 上,因此你的 reducer 逻辑可以引用这些 action types 且当 action 被派发时能对其作出响应。每一个 action 对象在 action.meta
字段中,都会含有当前唯一的 requestId
以及 args
值。
action creators 有如下这些签名:
处理这些在你的 reducers 的 actions 方法是,在 createReducer
或者 createSlice
中,使用对象键表示法或者 "builder 回调" 表示法引用这些 action creators。(注意,如果你使用 TypeScript,你应该使用 "builder 回调" 表示法类型能被正确推断 )
处理 Thunk 结果
暴露 Result Actions
当被派发时,thunks 有可能返回一个值。常见的使用案例是,从 thunk 返回一个 promise 对象,从组件中派发这个 thunk,并且在做额外的工作之前等待这个 promise 被 resolve:
createAsyncThunk
生成的 thunks 永远返回一个被 resolved的 promise 对象,其值为一个 fulfilled
的 action 对象或 rejected
的 action 对象。
调用的逻辑期望是把这些 actions 当成是原始的 promise 内容。Redux工具包 导出一个用于从 action 中抽取 payload
或 error
的 unwrapResult
函数,并且在适当的情况下返回或者抛出结果:
派发后检查错误
注意,这意味着 一个失败的请求或者 thunk 里面的错误 永远不会 返回 rejected 的 promise。在此,我们假定任何的失败都是一个被处理过的错误,而非未被处理的异常。这是因为我们想为那些不使用 dispatch
结果的使用者,去避免未被捕获的 promise rejections。
如果你的组件需要知道请求是否失败,请按照响应的情况使用 unwrapResult
处理被重新抛出的错误。
处理 Thunk 错误
当 payloadCreator
返回一个 rejected 的 promise 时 (例如一个在 async
函数中被抛出的错误),thunk 会派发一个 rejected
action,包含有一个自动被序列化的错误,作为 action.error
。然而,为了保证序列化,所有不满足 SerializedError
接口要求的事物都会被移除掉:
如果你需要自定义 rejected
action 的内容,你应该自己捕获这些错误,之后使用 thunkAPI.rejectWithValue
工具函数 返回 一个新的值。使用 return rejectWithValue(errorPayload)
会让 rejected
action 使用该值作为 action.payload
。
如果你的 API 响应 "成功",rejectWithValue
也应当被使用,但是应该含有某些额外能让 reducer 知道的错误细节。这种做法在这预期 API 字段级别有效性错误的时候是非常常见的。
取消操作
执行前取消
如果你需要在 payload creator 被调用前取消 thunk,你可以在 payload creator 后面提供一个 condition
回调函数作为一个选项。这个回调会接收 thunk 参数和一个 {getState, extra}
对象,以供决定是否继续执行使用。如果执行需要被取消,condition
回调会返回一个字面量 false
值:
如果 condition()
返回 false
,默认行为是没有 actions 会被派发。如果当 thunk 被取消时,你仍然需派发一个 "rejected" action,传入 {condition, dispatchConditionRejection: true}
。
运行期间取消
如果你需要在 thunk 执行完成之前取消,你可以使用 dispatch(fetchUserById(userId))
返回的 promise 里面的 abort
方法。
一个现实案例长这样:
- TypeScript
- JavaScript
在 thunk 以这种方式被取消之后,它会派发(以及返回)一个 "thunkName/rejected"
action,其中 error
属性会有一个 AbortError
的类型。这个 thunk 不会再继续派发任何后续的 actions。
另外,你的 payloadCreator
可以使用 AbortSignal
,它通过 thunkAPI.signal
被传递出来以取消一个昂贵的异步 action。
现代浏览器的 fetch
api 已经支持 AbortSignal
类型:
- TypeScript
- JavaScript
检查取消状态
读取 Signal 值
你可以使用 signal.aborted
属性去检查 thunk 是否被中断,如果是的话可以终止昂贵的用时太长的任务:
- TypeScript
- JavaScript
监听中断事件
你也可以调用 signal.addEventListener('abort', callback)
去编写当 promise.abort()
被调用时 thunk 里面的逻辑能接收到通知。这种做法,比如说,能和 axios 的 CancelToken
结合使用:
- TypeScript
- JavaScript
示例
- 利用加载状态,通过用户ID获取用户信息,以及一次只发一个请求:
在组件中使用 rejectWithValue 获取自定义 rejected 的 payload
注意: 这是假定我们的 userAPI 只抛出合法性方面错误的情况下的一个人为的例子
- TypeScript
- JavaScript