2

I'm struggling with old data being displayed when queries take longer than they should and return late or out of order.

This is NOT about how to call an async function or get query results. It's about how to either cancel/abort an async call or what tactic makes the best programming strategy for detecting out of-order query results as a result of multiple async processes.

Context: in one of my Svelte components, in the script section I have an await call that wraps around the fetch API to get data from a database. Simplified, my component looks like this:

<script>
  export let modelEntityId, entityNode, accessorDelegates

  let data

  async function runQuery() {
    const newQuery = new Query()

    // set up query params
    newQuery.setRequest({statement: entityNode.statementName,
                         columns: entityNode.columns,
                         filters: {[entityNode.entityIdColumn]: modelEntityId}})

    // submit the query by calling a top-level function in App.svelte which uses the fetch API
    if (await accessorDelegates.getQueryData(newQuery)) {

      // assign query results to local component variable
      data = newQuery.response.data
    }
  }

  $: modelEntityId && runQuery()
</script>

<!--- display data --->

Now modelEntityId provides my data key, determining which record I'll pull from the database, and this comes down via prop into my component. When the user selects a different entity, the prop reacts, calls the async runQuery function, which initiates the query then sits on await accessorDelegates.getQueryData awaiting results. When the results return, they are assigned to a local variable and that reacts the DOM below and displays.

But I have some queries that need tuning... they take a minute, and if the user loses interest and clicks away to a different entity (modelEntityId changes), a new query is executed to get that data, while the old query is still out there somewhere. Then the old query completes, and it is reacting the DOM and displaying its data while the user thinks they should be seeing data from the new query. Worse, sometimes the new query returns first and displays, then a bit later the older query returns and overwrites the newer display values with the older data.

I'm having trouble understanding this. Is it due to async? Am I ending up with two processes in a race condition that can both react the DOM the user sees depending on which query finishes first?

If this is the case, what's the best way to deal with this? I would not want the user to see the first query's results if they've abandoned it and gone on to a new query. I can certainly do a manual check of the filter values I requested and compare them with the values retrieved to make sure they match, but I'm interested to know if there's a better way. Should I be looking into how to cancel/abort any old async function calls whenever the function is called anew?

Paul W
  • 5,507
  • 2
  • 2
  • 13
  • You need to use `await runQuery(...)` to wait for the async operation to complete. – Barmar May 01 '23 at 19:47
  • You can use an async IIFE to wrap an async function around the code that uses it. Or use `.then()`. – Barmar May 01 '23 at 19:51
  • Please reopen my question. The other question you referred to does NOT even remotely answer my issue. I am NOT asking how to get the result of an async function. I have hundreds of async functions through my project and know exactly how to get the result. I'm asking about out of order issues and how best to tame them. – Paul W May 01 '23 at 19:51
  • Of course you are. `accessorDelegates.getQueryData(newQuery)` is an async function. The result is in `newQuery.response.data`. You're asking how to return that to the global variable `data`. – Barmar May 01 '23 at 19:55
  • I've replaced it with another duplicate related to modifying variables in an async function. – Barmar May 01 '23 at 19:56
  • I am asking how to detect that an async process has been replaced by another version of the same async function, so the aborted one can avoid doing anything. – Paul W May 01 '23 at 19:56
  • Sorry, didn't read fully. – Barmar May 01 '23 at 19:57
  • What you need to do is set a variable when you want to "interrupt" the first query. Then you can check that variable before assigning `data` when the first query returns. – Barmar May 01 '23 at 19:59
  • 1
    With [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) you can abort requests performed by `fetch`. Have you tried this? – JSON Derulo May 01 '23 at 20:01
  • @Barmar, Okay, makes sense, but such a variable should only impact the older query and not the newer one. To differentiate, the variable value would have to identify which query it's referring to, which is no different than simply checking my key value (modelEntityId) against the data returned to see if it matches. That's a solution, I'm just wondering if that's the best solution. It seems a little overkill to have to ask every query result "are you really the query I think you are?" before working with its results. – Paul W May 01 '23 at 20:03
  • I don't think there's any simple built-in solution. A more general mechanism might be to create an `InterruptiblePromise` class that adds the flag property. – Barmar May 01 '23 at 20:05
  • Race conditions are very general problem with async loading of data, so this is not really specific to Svelte. There should already be various questions on how to deal with this. – H.B. May 02 '23 at 08:31
  • Resembles [Getting the latest data from a promise returning service called repeatedly](https://stackoverflow.com/q/39824408/4543207) question. Perhaps my [answer](https://stackoverflow.com/a/39872802/4543207) there can help you. Also I believe more flexiable solution to solve this kind of hairy tasks is employing an Asynchronous Queue. I had written a [library](https://github.com/kedicesur/AQ) for this kind of tasks but been too lazy to finalize with a stable version. Still not bad. Especially read the 2. item in the Use Cases. – Redu May 03 '23 at 00:09

0 Answers0