Reputation: 116
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
.
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
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