DAK
DAK

Reputation: 116

How can I pause execution of function that queries API until API has finished updating from prior fetch call?

Background

I have an email-client (Single Page Application) containing various mailboxes, where the emails in each mailbox are represented by JSON objects.

Users can send, delete and archive emails by sending a fetch request to an API. For example, to delete an email, a fetch request is sent via PUT method, updating the deleted property for that email to deleted=true.

Similarly, to display a mailbox, the API is queried to request emails based on a specified property. For example, the inbox view displays all emails with deleted=false and archived=false.


Current Design Problem

To delete an email, first the JSON data for that email is requested from the API. The response is then passed to the delete_email() function, which executes a second API request to update the deleted property for the email to true.

At this point, I want the load_email() function to pause until the API has finished updating, so that when the inbox is loaded, it does not include the email that was just deleted.

As currently constructed, the load_mailbox('inbox') function sometimes executes before the delete_email() API query has completed. Therefore, when the inbox first loads after deleting an email, the inbox still displays the deleted email (which is only removed once the page is refreshed).

Call to delete_email() and load_mailbox() triggered by click event.

fetch(`/emails/${email_id}`)
.then(response => response.json())
.then(email => {
  delete_email(email);
  load_mailbox('inbox')
  location.reload();

delete_email()

function delete_email(email) {
  // Move email to trash if not currently deleted.
  if (email.deleted === false) {
    fetch(`emails/${email.id}`, {
      method: 'PUT',
      body: JSON.stringify({
        deleted: true,
        archived: false
      })
    })
  }

Edit: When applying the answer provided by @David, using async, I still get the same error I received when using the .then() structure. It seems to work properly for the if condition of the delete_email() function – moving emails from inbox to trash.

async function delete_email(email) {
  // Move email to trash if not currently deleted.
  if (email.deleted === false) {
    await fetch(`emails/${email.id}`, {
      method: 'PUT',
      body: JSON.stringify({
        deleted: true,
        archived: false
      })
    })
  }

But for the else clause, returning emails to the inbox from trash, where the only difference is: deleted=false, displays multiple duplicates of each email in the inbox when the inbox is first loaded. It correctly updates once the page is refreshed.

  // If email is in trash, remove back to inbox.
    else {
      await fetch(`emails/${email.id}`, {
        method: 'PUT',
        body: JSON.stringify({
          deleted: false,
          archived: false
        })
      })
    }
  } 

This uses the async version from David's answer:

async function get_email() {
  const response = await fetch(`/emails/${email_id}`);
  const email = await response.json();
  return email
}
get_email().then(async (email) => {
  await delete_email(email);
  load_mailbox('inbox');
})

Related SO Posts: Similar questions have been asked many times, but I have not been able to adapt them to my situation.

Using setTimeout(): Wait 5 sec. before executing next line

Using async "caller": Waiting for fetch to execute before next instruction

Calling multiple async functions: Call one async after another

Function called after fetch: Function returns before fetch

Using anonymous .then(): Calling method after fetch

Upvotes: 0

Views: 64

Answers (1)

David
David

Reputation: 219037

First, make delete_email an async function. This allows it to be awaitable:

async function delete_email(email) {
  // Move email to trash if not currently deleted.
  if (email.deleted === false) {
    await fetch(`emails/${email.id}`, {
      method: 'PUT',
      body: JSON.stringify({
        deleted: true,
        archived: false
      })
    })
  }
}

(You could also not use async and instead return the resulting Promise from fetch, but you'd also need to ensure that you return a Promise when that if condition isn't met, as you'll want this function to always return a Promise.)

Note how it internally uses await on the fetch operation. The main difference now is that delete_email returns a Promise. Which you can await:

.then(async (email) => {
   await delete_email(email);
   load_mailbox('inbox');
})

Or, continuing with the use of .then() structures:

.then(email => {
   return delete_email(email);
})
.then(() => {
   load_mailbox('inbox');
})

Basically, from the lengthy question it seems that you're drastically overthinking it. Any asynchronous operation you create should return a Promise. That Promise can be awaited with await (in an async function) or followed up with .then() (in a non-async function).

Upvotes: 2

Related Questions