英文:
How can I pause execution of function that queries API until API has finished updating from prior fetch call?
问题
背景
我有一个电子邮件客户端(单页应用程序),其中包含各种邮箱,其中每个邮箱中的“电子邮件”由JSON对象表示。
用户可以通过向API发送fetch请求来发送、删除和归档电子邮件。例如,要删除电子邮件,会通过PUT
方法发送fetch请求,将该电子邮件的已删除属性更新为deleted=true
。
类似地,要显示邮箱,会查询API以根据指定属性请求电子邮件。例如,收件箱视图显示所有deleted=false
和archived=false
的电子邮件。
当前设计问题
要删除电子邮件,首先需要从API请求该电子邮件的JSON数据。然后,响应传递给delete_email()
函数,该函数执行第二个API请求,将电子邮件的deleted
属性更新为true
。
在这一点上,我希望load_email()
函数在API完成更新之前暂停,以便当收件箱加载时,不包括刚刚删除的电子邮件。
目前的构建方式是,load_mailbox('inbox')
函数有时在delete_email()
API查询完成之前执行。因此,在删除电子邮件后首次加载收件箱时,收件箱仍然显示已删除的电子邮件(只有在刷新页面后才会移除)。
通过click
事件触发对delete_email()
和load_mailbox()
的调用。
fetch(`/emails/${email_id}`)
.then(response => response.json())
.then(email => {
delete_email(email);
load_mailbox('inbox')
location.reload();
delete_email()
function delete_email(email) {
// 如果尚未删除电子邮件,则将其移到垃圾箱。
if (email.deleted === false) {
fetch(`emails/${email.id}`, {
method: 'PUT',
body: JSON.stringify({
deleted: true,
archived: false
})
})
}
编辑: 当应用@David提供的答案时,使用async
,我仍然收到与使用.then()
结构时收到的相同错误。对于delete_email()
函数的if
条件 – 从收件箱移动电子邮件到垃圾箱 – 似乎可以正常工作。
async function delete_email(email) {
// 如果尚未删除电子邮件,则将其移到垃圾箱。
if (email.deleted === false) {
await fetch(`emails/${email.id}`, {
method: 'PUT',
body: JSON.stringify({
deleted: true,
archived: false
})
})
}
但是对于else
子句,将电子邮件从垃圾箱返回到收件箱,唯一的区别是:deleted=false
,在首次加载收件箱时,会显示每封电子邮件的多个重复副本。只有在刷新页面后才会正确更新。
// 如果电子邮件在垃圾箱中,请将其还原到收件箱。
else {
await fetch(`emails/${email.id}`, {
method: 'PUT',
body: JSON.stringify({
deleted: false,
archived: false
})
})
}
}
这是来自David答案的async
版本:
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');
})
相关SO帖子: 类似的问题已经被问过很多次,但我还没有能够将它们适应我的情况。
使用setTimeout()
:等待5秒钟后执行下一行
使用async
“调用者”:等待fetch完成后再执行下一条指令
调用多个async
函数:在一个async函数执行后调用另一个函数
在fetch
后调用函数:在fetch请求完成之前函数返回
使用匿名.then()
:在fetch执行后调用方法
英文:
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
答案1
得分: 2
首先,将delete_email
改为async
函数。这样可以使用await
等待它:
async function delete_email(email) {
// 如果电子邮件未被删除,将其移到垃圾箱。
if (email.deleted === false) {
await fetch(`emails/${email.id}`, {
method: 'PUT',
body: JSON.stringify({
deleted: true,
archived: false
})
});
}
}
(你也可以选择不使用async
,而是返回fetch
的结果Promise
,但在不满足if
条件时,你还需要确保返回一个Promise
,因为你希望这个函数始终返回一个Promise
。)
请注意它在fetch
操作上内部使用了await
。现在的主要区别是delete_email
现在返回一个Promise
,你可以使用await
等待它:
.then(async (email) => {
await delete_email(email);
load_mailbox('inbox');
})
或者,继续使用.then()
结构:
.then(email => {
return delete_email(email);
})
.then(() => {
load_mailbox('inbox');
})
基本上,从这个冗长的问题中看来,你可能在过度考虑。你创建的任何异步操作都应该返回一个Promise
。这个Promise
可以在async
函数中使用await
等待,或者在非async
函数中使用.then()
后续处理。
英文:
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论