简体   繁体   English

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

[英]How to make an atomic process in React (async/await)?

Imagine a post that can be liked when pressing a button.想象一下按下按钮就可以点赞的帖子。 This button modifies a remote database, so it will take a little while to associate the like to the specific post.此按钮会修改远程数据库,因此将喜欢的内容关联到特定帖子需要一些时间。

Now, if an user starts pressing the button so fast with this code:现在,如果用户使用以下代码开始如此快速地按下按钮:

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

As all is async, it is possible to see this situation:由于一切都是异步的,因此可以看到这种情况:

Liking喜欢

Disliking不喜欢

DONE <---------- Disliking done完成<---------- 不喜欢完成

DONE <---------- Liking done完成<---------- 喜欢完成

I have thought to create a state "isLiking" to avoid running the code until all the async job has finished.我想创建一个状态“isLiking”以避免在所有异步作业完成之前运行代码。 Something like this:像这样的东西:

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

With this all is going OK, but if the user press the button fast, he will not be able to see the GUI changes (the like button color (red if is liked, white if not)) until all the process described in the code above finishes.一切顺利,但如果用户快速按下按钮,在代码中描述的所有过程之前,他将无法看到 GUI 更改(喜欢按钮颜色(红色表示喜欢,白色表示不喜欢))以上完成。

I have also thought to make a debounced function (for the handlePress) like this:我还想过像这样制作一个去抖动函数(对于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

But with this, the only thing I do is decrease the chance of getting messy results.但是有了这个,我唯一要做的就是减少获得混乱结果的机会。 That is, I still have the same problem as with the first code.也就是说,我仍然遇到与第一个代码相同的问题。

Any idea to do what I want in such a way that the results I get are ordered and avoiding the little wait for writing to the database?任何想法以这样的方式做我想做的事情,即我得到的结果是有序的并避免等待写入数据库的时间?

Thank you.谢谢你。

The solution is to disable the button immediately.解决方案是立即禁用该按钮。 Using setState , you cannot expect immediate update of isLinking , and that's why you are annoyed.使用setState ,您不能指望立即更新isLinking ,这就是您感到恼火的原因。 One of the solution is to use flag variable instead of using state .解决方案之一是使用flag variable而不是使用state

You can fix in this way.您可以通过这种方式修复。

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

The @Prime's answer works, but it falls short when you have actions that are scattered all over the app and it would be hard to synchronize everything. @Prime 的答案有效,但是当您的操作分散在整个应用程序中并且很难同步所有内容时,它就不够用了。

In my case it was API token refreshing.就我而言,它是 API 令牌刷新。 As API requests are scattered all over the app, it is nearly impossible to block the call with a state variable.由于 API 请求分散在整个应用程序中,因此几乎不可能使用状态变量来阻止调用。

Therefore I present another solution:因此,我提出了另一种解决方案:

/*
    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;
      });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM