如何暂停查询API的函数执行,直到API从之前的获取调用中完成更新?

huangapple go评论66阅读模式
英文:

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=falsearchived=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).

huangapple
  • 本文由 发表于 2023年6月22日 04:36:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76526963.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定