如何在React中使用原子处理(async/await)?

5

想象一篇文章,当按下按钮时可以被点赞。这个按钮会修改远程数据库,所以将花费一些时间将喜欢的内容关联到具体的帖子上。

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

 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");
  };

由于所有操作都是异步的,可能会出现以下情况:

喜欢

不喜欢

DONE <---------- 不喜欢已完成

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

但是这样做,我唯一能做的就是减少出现混乱结果的机会。也就是说,我仍然面临着与第一个代码相同的问题。
有没有什么办法可以让我按顺序获取结果,并避免在写入数据库时等待过长?
谢谢。
2个回答

5
解决方案是立即禁用按钮。使用setState,您不能期望isLinking的立即更新,这就是为什么让您感到烦恼的原因之一。 其中一种解决方法是使用标记变量而不是使用状态。 您可以通过以下方式进行修复。
 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");
  };

0

@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;
      });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接