Search code examples
javascriptvue.jsasync-awaitvuexrace-condition

If an async Vuex action is run twice, can the code following the await call be run in random order?


const actions = {
  search: debounce(
    async ({ commit, dispatch, getters, rootGetters }, { page = 1 }) => {
      commit("setLoading", true);
      commit("setPage", page);

      console.log("Starting...")
      const randId = Math.random() * 100;
      console.log(randId);

      const results = await EventApi.get();

      console.log("Done: ", randId, `|${results.meta.slept}|`)

      // After `await`
      commit(page == 1 ? "setEvents" : "addEvents", results.events);
      // ... and a bunch more commits after that.

    }, 300, { leading: true, trailing: true })
  }
}

If the action above is called twice:

store.dispatch('search', { page: 1 });
store.dispatch('search', { page: 1 });

Let's assume the request made by the first call takes 10s to complete, and the request made by the second one takes only 1 second.

Is the code after await EventApi.get() in the second call executed after the one in the first call, even thought the request should have finished way earlier? I wouldn't have thought so but my experiment showed that it was the case, so something must be wrong with my code.

Here are the Vuex logs + some of my comments:

https://gist.github.com/niuage/9e2dcd509dc6d5246b3a16af56ccb3d3


In case it helps, here's the simplified EventApi module:

const get = async () => {
  const q = await fetch(someUrl);
  const result = await q.json();

  return result;
}

export default {
  get
}

I'm asking because I'm having an issue where, super rarely, I get my result count out of sync with the results actually displayed, so I was guessing that it could be because there was a race condition between multiple requests.


Solution

  • The second, shorter, call in your example would complete first. This is because the await only waits within the context of the async function.

    Since async/await is a different way of writing promises, you can think of it as 2 separate function calls that return a promise. The one would not delay the other.

    Here's a demo:

    const delay = time => new Promise(resolve => setTimeout(() => resolve(), time))
    
    async function wait(id, time) {
      await delay(time);
      console.log(id + ' complete.');
    }
    
    console.log('Starting 1...');
    wait('#1', 5000);
    console.log('Starting 2...');
    wait('#2', 1000);
    View the bottom portion of the demo for the console results.

    Here's the async function from the demo above written with .then instead:

    function wait(id, time) {
      return delay(time).then(() => {
        console.log(id + ' complete.');
      });
    }
    

    If you wanted to force the second function call in your example to wait for the first, make the calling context of both an async function, for example:

    async created() {
      // Assuming the `search` action returns its http promise!
      await this.$store.dispatch('search', { page: 1 });
      await this.$store.dispatch('search', { page: 1 });
      ...
    }