如何在 React (async/await) 中创建原子进程?

Vic*_*ina 5 javascript asynchronous async-await reactjs react-native

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

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

 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");
  };
Run Code Online (Sandbox Code Playgroud)

由于一切都是异步的,因此可以看到这种情况:

喜欢

不喜欢

完成<---------- 不喜欢完成

完成<---------- 喜欢完成

我想创建一个状态“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");
  };
Run Code Online (Sandbox Code Playgroud)

一切顺利,但如果用户快速按下按钮,在代码中描述的所有过程之前,他将无法看到 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
Run Code Online (Sandbox Code Playgroud)

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

任何想法以这样的方式做我想做的事情,即我得到的结果是有序的并避免等待写入数据库的时间?

谢谢你。

Pri*_*ime 5

解决方案是立即禁用该按钮。使用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");
  };
Run Code Online (Sandbox Code Playgroud)