如何让 React Redux 异步操作返回一个承诺?

Posted

技术标签:

【中文标题】如何让 React Redux 异步操作返回一个承诺?【英文标题】:How to make React Redux async action return a promise? 【发布时间】:2021-06-20 00:29:00 【问题描述】:

我有一个返回承诺的 redux 操作函数。在该函数体内,还有另一个异步函数,它在完成调用时返回一个回调。然后我在另一个地方调用这个函数,用.then()链接它,但是当在浏览器中用断点调试它时,promise函数存在于前两行代码之后

return new Promise((resolve, reject) => 
    return (dispatch, getState) => 

是因为第二个return语句吗?这是react/redux 代码,所以我必须为我的异步 Thunk redux 操作提供第二个返回语句。我怎样才能做到这一点?这样我就可以在另一个 redux 操作中调用它并使用 handleProfileImageUploadToS3().then(() => ...) 链接它?

完整的函数体:

export const handleProfileImageUploadToS3 = () => 
  return new Promise((resolve, reject) => 
    return (dispatch, getState) => 
      const settingsState = getState().BusinessSettings
      const userState = getState().User
      const imageUpload = true

      if (!settingsState.get('logoImage') || settingsState.get('logoImage') === null) 
        reject('no image selected')
        return
      
      Utilities.uploadFileToS3(
        imageUpload,
        settingsState.get('logoImage'),
        'apiurl',
        `profiles/$userState.get('id')`,
        (error, url) => 
          if (error) 
            dispatch(uploadProfileSettingsImageError(error))
            return
          
          dispatch(updateProfileImageUrlAfterUpload(url))
          resolve(url)
        
      )
    
  )

【问题讨论】:

【参考方案1】:

我没有看到你的整个代码库,但我担心这个函数中有一些危险信号。

您的状态的BusinessSettingsUser 属性似乎是带有get() 方法的non-serializable 对象。 如果 S3 上传出现错误,那么你 dispatchreturn 但你永远不会 resolvereject Promisereject('no image selected') 的拒绝不太可能在任何地方被发现。 似乎未上传的图像应该存储在您的 UI 状态中并作为参数传递,而不是存储在 Redux 中。您可能希望在 URL 上传后存储它。这也消除了拒绝的需要。

您在此函数中解决了两个不同的问题,我建议您将两者分开。

首先,您有一个使用成功/失败回调的函数 Utilities.uploadFileToS3,并且您希望将其转换为异步函数(返回 Promise 的函数)。

我会创建一个助手,它只接受变化的参数并忽略那些不变的参数(如'apiurl')。

const asyncUploadFileToS3 = (image, userId) => 
  return new Promise((resolve, reject) => 
    Utilities.uploadFileToS3(
      true,
      image,
      "apiurl",
      `profiles/$userId`,
      (error, url) => (url ? resolve(url) : reject(error))
    );
  );
;

现在您已经解决了这部分问题,您可以以更典型的方式编写 thunk。您可以通过.then() 链接或通过创建函数async 并使用try/catch 块来返回Promise。我们不需要将整个内容包装在 new Promise 中,因为我们已经在 asyncUploadFileToS3 函数中处理了这个问题。

可以从 thunk 中返回一个结果并将其链接起来,但我不确定这里实际上最有意义的是什么。

export const handleProfileImageUploadToS3 = (image) => 
  async ( dispatch, getState ) => 
    const userId = getState().user.id;
    try 
      const url = await asyncUploadFileToS3(image, userId);
      dispatch(updateProfileImageUrlAfterUpload(url));
      return "this is the result";
     catch (error) 
      dispatch(uploadProfileSettingsImageError(error));
      return "there was an error";
    
  ;
export default function App() 
  const dispatch = useDispatch();

  const avatar = useSelector((state) => state.user.imageUrl);

  const onClick = () => 
    const image = new Blob();
    dispatch(handleProfileImageUploadToS3(image)).then(
      // logs "this is the result" or "there was an error"
      (returned) => console.log("finished", returned)
    );
  ;

  return (
    <div>
      <button onClick=onClick>Upload</button>
      avatar ? <div>Avatar URL: avatar</div> : <div>No Avatar</div>
    </div>
  );

Code Sandbox Demo

【讨论】:

感谢您的全面回答。有很多东西要吸收。 “似乎未上传的图像应该存储在您的 UI 状态中并作为参数传递,而不是存储在 Redux 中。”我实际上是这样做的。我将图像 blob 存储在本地状态,并且仅在单击按钮以保存时将其保存到 redux 存储。我将 File 对象保存在我用来上传到 S3 的 redux 存储中,取回 url,将存储从 File 对象更新为 S3 生成的 URL,然后调用另一个操作将该 URL 保存到数据库。【参考方案2】:

经过更多研究,我发现您必须在返回 new Promise 之前从初始 Promise 函数返回另一个函数才能访问 Redux dispatchgetState 方法,而不是在 @987654325 内将其作为匿名函数返回@ 代码块,然后退出函数执行。完整代码如下:

export const handleProfileImageUploadToS3 = () => (dispatch, getState) => 
  return new Promise((resolve, reject) => 
    const settingsState = getState().BusinessSettings
    const userState = getState().User
    const imageUpload = true

    if (!settingsState.get('logoImage') || settingsState.get('logoImage') === null) 
      reject('no image selected')
      return
    
    Utilities.uploadFileToS3(
      imageUpload,
      settingsState.get('logoImage'),
      'apiurl',
      `profiles/$userState.get('id')`,
      (error, url) => 
        if (error) 
          dispatch(uploadProfileSettingsImageError(error))
          return
        
        dispatch(updateProfileImageUrlAfterUpload(url))
        resolve(url)
      
    )
  )

【讨论】:

您是否需要在if(error) 中添加resolvereject?似乎它可能永远挂在那里。如果是我个人,我会分别解决这两个问题。这些问题是 1)您需要围绕 Utilities.uploadFileToS3 函数创建一个 Promise 包装器,因为它不是 async 和 2)您需要创建一个使用该函数的 thunk 调度。当您 reject 在这里“未选择图像”时,是否在任何地方被捕获? settingsState 是您的 Redux 状态的一部分,但您正在调用 settingsState.get('logoImage'),这对我来说很奇怪,因为您的 Redux 状态应该是原始可序列化数据。它不应该有get() 方法。 (userState.get('id') 也一样)。 @LindaPaiste 它没有挂在Utilities.uploadFileToS3() callback,通过使用断点我发现该函数在返回(dispatch, getState) =&gt; 的第二行代码处运行我仍然不知道为什么尽管。而Utilities.uploadFileToS3() 本身就是一个异步函数,但它使用回调而不是承诺。如果失败,我是否也应该在其回调中使用拒绝? “2)您需要创建一个使用该功能的 thunk 调度” - 像我现在那样使用它与将它作为自己的动作调度有什么区别?和现在一样运行良好。 @LindaPaiste 我不确定 redux store 调用,我们在整个项目中都这样做,我最近被介绍到这个项目,所以我不确定有什么好处redux 的实践。这是减速器的外观:const getInitialState = () =&gt; return Map( logoImage: '', brandName: '', website: '', countryCode: '', currency: '', vat: '', ) 使用Map 不是一个好主意。只需存储该对象而无需在其周围添加Map。 With Redux, our application state is always kept in plain javascript objects and arrays. That means you may not put other things into the Redux state - no class instances, built-in JS types like Map / Set Promise / Date, functions, or anything else that is not plain JS data.

以上是关于如何让 React Redux 异步操作返回一个承诺?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决 react-redux 中的此错误“错误:操作必须是普通对象。使用自定义中间件进行异步操作。”

如何返回异步箭头功能?

React Redux - 动作必须是普通对象。使用自定义中间件进行异步操作

在 React 组件中订阅 redux 操作

react常见面试题

如何使用 thunk 和 useDispatch(react-redux 挂钩)从操作中返回承诺?