【问题标题】:Wait for 1 promise before all other promises在所有其他承诺之前等待 1 个承诺
【发布时间】:2020-04-22 11:09:48
【问题描述】:

提前为这个模棱两可的标题道歉。

为了更好地解释这个问题 - 我在 fetch-form-data.js 中有两个函数,看起来像......

let cachedBaseUrl;

const getBaseUrl = () => {
  if (cachedBaseUrl) return Promise.resolve(cachedBaseUrl)

  return fetch('https://some-baseurl-endpoint.com')
    .then(baseUrl => {
      cachedBaseUrl = baseUrl
      return baseUrl
    });
}

const getFormData = formId =>
  getBaseUrl()
    .then(baseUrl => fetch('https://form-data-endpoint.com'))

module.exports = {
  getFormData
}

getFormData 被导出并在其他地方循环调用数百次。问题是getFormData 依赖于getBaseUrl,所以调用getFormData 数百次也调用getBaseUrl 数百次,这经常出错,因为我猜baseUrl 端点不喜欢这么快被击中。

我想确保getBaseUrl 只被击中一次,并让所有后续getFormDatagetBaseUrl 的调用“等待”在继续之前解决。

一种解决方案是在使用getFormData 的地方同时导出getBaseUrlgetFormData,等待getBaseUrl,然后根据需要多次调用getFormData。有点像...

async () => {
  const baseUrl = await getBaseUrl();
  const formData = [];

  for (let form of forms) {
    formData.push(getFormData(form));
  }

  await Promise.all(formData);
  ...
}

但是,getFormData 的使用分布在整个代码库中。像当前实现一样简单地抽象出getBaseUrl 并只公开getFormData 会很好,但要解决“等待”所有后续getFormData 调用的问题,直到至少第一个getBaseUrl 调用得到解决。

我的微弱尝试是缓存 baseUrl,如上面的代码块所示,但这并不会立即“停止”随后的 getBaseUrl 调用发生,它会调用另一个 fetch,因为尚未缓存任何内容。

我将如何实施这样的事情?

【问题讨论】:

  • GetBaseUrl()中缓存Base Url?哦,等等。
  • 所以你的问题是你的缓存不起作用,对吧?
  • 我的微弱尝试是缓存 baseUrl,如上面的代码块所示,但这并不能“阻止”立即发生后续的 getBaseUrl 调用,因为尚未缓存任何内容,它会调用另一个 fetch。跨度>
  • cachedBaseUrl 最终在端点返回 baseUrl 后变为 true,这往往需要 1-2 秒。在这 1-2 秒之前,getBaseUrl 已经被调用了数百次,这当然会触发 fetch 数百次,因为在返回第一个响应之前,cachedBaseUrl 仍然是 false
  • 缓存 Promise 而不是它解析的值。

标签: javascript promise


【解决方案1】:

试试这个

let cachedBaseUrl;
const getBaseUrl = () => {
  if (cachedBaseUrl) return cachedBaseUrl
  return cachedBaseUrl = new Promise((resolve, reject) =>
      fetch('https://some-baseurl-endpoint.com')
          .then(BaseUrl => { cachedBaseUrl = undefined; resolve(BaseUrl) })
          .catch(() => { cachedBaseUrl = undefined; reject() })
  );
}

您需要在第一个请求完成之前阻止getBaseUrl() 发送其他请求。所以你需要一个那个请求的Promise,而不是Promise.resolve(constant)

请求完成后,您将其结果设置为resolve/reject 并重置cachedBaseUrl,这样您就可以传递if 并重试。

重置逻辑由您决定。您可以不断地但按顺序获取baseurl-endpoint(就像现在所做的那样),或者不时地,那么您将不得不调用SetTimeout(interval, () => cachedBaseUrl = undefined) 而不仅仅是cachedBaseUrl = undefined。或者做任何你喜欢的事情。

【讨论】:

  • 那有什么作用?
  • @Robert Harvey 进行了编辑,希望这能澄清我的回答
【解决方案2】:

也只需缓存你的承诺。

let cachedBaseUrl, cachedPromise;

const getBaseUrl = () => {
  if (cachedBaseUrl) return Promise.resolve(cachedBaseUrl)
  if (cachedPromise) return cachedPromise;

  cachedPromise = fetch('https://some-baseurl-endpoint.com')
    .then(baseUrl => {
      cachedBaseUrl = baseUrl
      return baseUrl
    });

  return cachedPromise;
}

const getFormData = formId =>
  getBaseUrl()
    .then(baseUrl => fetch('https://form-data-endpoint.com'))

module.exports = {
  getFormData
}

【讨论】:

  • 没有解决他需要不压倒 getBaseUrl API 端点的问题(根据他的一个 OP cmets)
  • @Oliver 我已经测试过并且不会压倒 getBaseUrl API 端点,只需调用 1 次。
  • 他正在快速连续调用数百次 getFormData(需要 getBaseUrl)。在这种情况下,他需要设置状态或其他设置,以防止数百个 API 调用使端点过载。尽管AFAIK,否则您的代码可以正常工作
  • @Oliver,你错了。在此解决方案中对 getBaseUrl 的后续调用中,if (cachePromise) return ... 条件会阻止发送其他请求。
  • @Oliver 我想你可能会错过 fetch(...).then(...) 同步评估为 Promise 对象,该对象同步填充 cachedPromise。一个 Promise 对象即使在它没有被解析的情况下也是真实的,所以这里建议的 getBaseUrl 将返回缓存的 Promise 而不会向 API 发出更多请求
【解决方案3】:

您的缓存解决方案是一个好的开始,通过缓存待处理的承诺,我认为它可以优雅地解决问题。

let cachedBaseUrl;

const getBaseUrl = () => {
  if (cachedBaseUrl) return Promise.resolve(cachedBaseUrl)
  return cachedBaseUrl = fetch('https://some-baseurl-endpoint.com');
}

const getFormData = formId =>
  getBaseUrl()
    .then(baseUrl => fetch('https://form-data-endpoint.com'))

module.exports = {
  getFormData
}

【讨论】:

    【解决方案4】:

    致电getBaseUrl() 一次并保存您从中获得的承诺。然后在 getFormData() 中,调用 then 以保存已保存的承诺。

    const getBaseUrl = () => ...
    
    const baseUrlPromise = getBaseUrl()
    
    const getFormData = formId =>
      baseUrlPromise
        .then(...)
    

    在一个promise上多次使用.then(...)可以做出多个依赖它的promise。

    如果没有其他使用cachedBaseUrl,您可以将其删除,因为解析后的值将保存为baseUrlPromise 的一部分。

    【讨论】:

    • @RobertHarvey 它确实缓存了 URL。问题是,如果你要在一个循环中调用它,许多getBaseUrls() 会在一个完成之前排队。缓存是在第一个解决后设置的,但所有其他待处理的承诺仍然会解决,向同一个端点发送垃圾邮件。这是提问者试图避免的问题。
    • @Klaycon:已经指出here
    • 澄清:早期的 cmets 已被删除。不要循环调用此答案中的代码 sn-p 。它是模块顶级代码。可以反复拨打getFormData
    猜你喜欢
    • 2017-06-24
    • 1970-01-01
    • 2022-11-21
    • 2021-10-10
    • 2014-06-14
    • 2018-03-19
    • 1970-01-01
    • 2019-03-31
    • 2014-03-12
    相关资源
    最近更新 更多