【问题标题】:How to make an atomic process in React (async/await)?如何在 React (async/await) 中创建一个原子进程?
【发布时间】:2021-01-31 20:51:12
【问题描述】:

想象一下按下按钮时可以点赞的帖子。此按钮会修改远程数据库,因此将点赞关联到特定帖子需要一点时间。

现在,如果用户开始使用此代码快速按下按钮:

 state = {
    isLiked: false,
 }

 handlePress = () => {
    this.setState(
      {
        isLiked: !this.state.isLiked,
      },
      this.handleLike
    );
  };

  handleLike = async () => {
    const { postId } = this.props;

    try {
      console.log(isLiked ? "Liking" : "Disliking")
      await db.processLike(postId);
    } catch (err) {
      // If an error has occurred, reverse the 'isLiked' state
      this.setState({
        isLiked: !this.state.isLiked,
      });

      // TODO - Alert the error to the user in a toast
      console.log(err);
    }

    console.log("DONE");
  };

由于一切都是异步的,所以有可能看到这种情况:

喜欢

不喜欢

完成

完成

我曾考虑创建一个状态“isLiking”以避免在所有异步作业完成之前运行代码。像这样的:

 state = {
    isLiking: false,
    isLiked: false,
 }

 handlePress = () => {

    if (this.state.isLiking) return; <------------------------------------

    this.setState(
      {
        isLiking: true, <------------------------------------
        isLiked: !this.state.isLiked,
      },
      this.handleLike
    );
  };

  handleLike = async () => {
    const { postId } = this.props;

    try {
      console.log(isLiked ? "Liking" : "Disliking"); 
      await db.processLike(postId);
    } catch (err) {
      // If an error has occurred, reverse the 'isLiked' state
      this.setState({
        isLiked: !this.state.isLiked,
      });

      // TODO - Alert the error to the user in a toast
      console.log(err);
    }

    this.setState({ isLiking: false }); <------------------------------------

    console.log("DONE");
  };

这样一切正常,但如果用户快速按下按钮,他将无法看到 GUI 变化(喜欢按钮颜色(喜欢红色,如果不喜欢,则白色)),直到描述的所有过程在上面的代码中完成。

我也想过像这样制作一个去抖函数(用于handlePress):

export const debounce = (func, wait, immediate) => {
  /*
    Returns a function, that, as long as it continues to be invoked, will not
    be triggered. The function will be called after it stops being called for
    N milliseconds. If `immediate` is passed, trigger the function on the
    leading edge, instead of the trailing.
  */

  let timeout;
  return function () {
    let context = this,
      args = arguments;

    let later = function () {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };

    let callNow = immediate && !timeout;

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);

    if (callNow) func.apply(context, args);
  };
};

...

debuncedHandlePress = debounce(this.handlePress, 500); // Now, when the button is pressed, it will call this function, instead of the original handlePress

但是有了这个,我唯一要做的就是减少得到混乱结果的机会。也就是说,我仍然遇到与第一个代码相同的问题。

有什么想法可以按照我想要的方式来做我得到的结果是有序的并避免等待写入数据库的时间吗?

谢谢。

【问题讨论】:

    标签: javascript reactjs react-native asynchronous async-await


    【解决方案1】:

    解决方法是立即禁用该按钮。使用setState,您不能期望立即更新isLinking,这就是您生气的原因。 一种解决方案是使用flag variable 而不是state

    你可以这样修复。

     state = {
        isLiked: false,
     }
    
     constructor(props) {
        this.isLiking = false; <------------------------------------
     }
     
    
     handlePress = () => {
        this.isLiking = true; <------------------------------------
        this.setState(
          {
            isLiked: !this.state.isLiked,
          },
          this.handleLike
        );
      };
    
      handleLike = async () => {
        const { postId } = this.props;
    
        try {
          console.log(isLiked ? "Liking" : "Disliking"); 
          await db.processLike(postId);
        } catch (err) {
          // If an error has occurred, reverse the 'isLiked' state
          this.setState({
            isLiked: !this.state.isLiked,
          });
    
          // TODO - Alert the error to the user in a toast
          console.log(err);
        }
    
        this.isLiking = false; <------------------------------------
    
        console.log("DONE");
      };
    

    【讨论】:

      【解决方案2】:

      @Prime 的答案有效,但当您的操作分散在整个应用程序中并且很难同步所有内容时,它就不够了。

      在我的例子中,它是 API 令牌刷新。由于 API 请求分散在整个应用程序中,因此几乎不可能使用状态变量来阻止调用。

      因此我提出另一种解决方案:

      /*
          The long running operation
      */
      
      const myLongRunningOperation = async () => {
          // Do an API call, for instance
      }
      
      /*
          Promise locking-queueing structure
      */
      
      var promiesCallbacks = [];
      
      const resolveQueue = value => {
        promiesCallbacks.forEach(x => x.resolve(value));
        promiesCallbacks = [];
      };
      const rejectQueue = value => {
        promiesCallbacks.forEach(x => x.reject(value));
        promiesCallbacks = [];
      };
      const enqueuePromise = () => {
        return new Promise((resolve, reject) => {
          promiesCallbacks.push({resolve, reject});
        });
      };
      
      /*
          The atomic function!
      */
      
      var actionInProgress = false;
      
      const doAtomicAction = () => {
          if (actionInProgress) {
            return enqueuePromise();
          }
      
          actionInProgress = true;
      
          return myLongRunningOperation()
            .then(({ access }) => {
              resolveQueue(access);
              return access;
            })
            .catch((error) => {
              rejectQueue(error);
              throw error;
            })
            .finally(() => {
              actionInProgress = false;
            });
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-31
        • 2020-10-04
        • 1970-01-01
        • 2018-10-29
        • 1970-01-01
        • 2020-11-12
        • 1970-01-01
        相关资源
        最近更新 更多