【问题标题】:How to go about caching promises when working with the new fetch API?使用新的 fetch API 时如何缓存 Promise?
【发布时间】:2016-10-16 17:10:56
【问题描述】:

有时你想在多个地方多次使用同一个承诺的对象。例如来自 URL 的 AJAX 资源的承诺。

对象很难缓存,因为你不知道它什么时候异步可用。

没关系,因为您可以call .then() multiple times on a single promise,这样您就可以在第一次需要它时缓存它,并在以后访问缓存。当它可用时,所有的.thens 都会被执行。

但是当 promise 来自 the new fetch API 时,它似乎并不那么简单......因为涉及到第二个 promise。

fetch() 本身会返回一个 promise,当它实现时,您必须使用 .blob().json() 等方法通过第二个 promise 访问 HTTP 响应的正文。这些是"Body mixin" 的一部分。

如何使用fetch()的香草示例:

fetch(url).then(function(resp) {
  return resp.json().then(function(json) {
    // use the JSON
  });
});

问题在于,即使您可以多次调用.then(),您不能在一个响应中多次调用.blob().json() 等函数。

看来.json() 等的结果也需要缓存,但这应该更困难,因为它只能异步使用。在两个异步承诺解决之前,资源可能会被请求两次 - 竞争条件。

我错过了什么吗?有没有一种简单的方法来缓存 fetch 的 promise 的 JSON 等,而不是我看不到的?我似乎在网上找不到任何地方讨论过这个问题。

【问题讨论】:

  • 您是否有理由不能使用变量来存储它?
  • 考虑到异步性,何时何地可以访问变量?
  • 不清楚你需要在什么范围内使用这个变量。在 fetch 调用之后的 then 块中,您可以在 return 语句上方定义一个变量,并将其分配到所述 return 语句的第一个 then 块中。然后同一个 fetch 块内的后续 return 语句可以访问该变量的内容。但是再一次,您的问题不清楚何时需要访问这些缓存结果。如果超出获取请求和任何后续链接,请指定。
  • 好吧,我的真实代码很复杂,涉及至少两个异步 fetch 调用循环来检索值,然后这些值将被过滤并相互交叉引用。这就是为什么我想将值存储在缓存中。当然,可以将缓存对象视为变量。但是一个循环将写入它,另一个循环将从它读取,并且都是异步的。我的问题试图解决一般问题,而不是对我的特定项目进行代码审查(-:
  • 我认为,如果您准确地解释了您想要发生的事情,您可能会得到更好的答案。 2个异步获取调用循环?这 2 个循环是否并行运行?过滤和“交叉引用”何时发生?更多细节真的会有所帮助。我现在可以从您的问题中收集到的唯一信息可能是您想使用 Promise.all 并等待两个循环完成,然后同步进行过滤/交叉引用,但是您要完成的再次看起来非常模糊。

标签: javascript caching promise race-condition fetch-api


【解决方案1】:

您不能在响应上多次调用.json().text(),因为这些方法中的任何一个都会消耗主体,但是您可以在使用它之前复制响应,这样您就可以简单地缓存承诺而不是这样做:

cachedFetch.then(response => response.json()).then(console.log);

做:

cachedFetch.then(response => response.clone().json()).then(console.log);

或者将您的 fetch 调用包装在一个始终返回响应克隆的函数中:

doFetch().then(response => response.json()).then(console.log);

地点:

var _cached;
function doFetch() {
  if (!_cached) {
    _cached = fetch('/something');
  }
  return _cached.then(r => r.clone());
}

【讨论】:

  • 其实我确实注意到了这个.clone() 方法,但我认为这会导致不必要的资源使用,所以没有进一步调查。
  • 另外,您的所有示例都应该使用cachedFetch(),还是应该前两个使用fetch()?最后一个应该是递归的吗?我不确定你是打错字还是我不明白。
  • 没有。在第一个示例中,我假设您在其他地方调用了 fetch(),并将返回的承诺放在 cachedFetch 中。在最后一个示例中,函数在放置器中完成所有操作。无论如何,我将答案更改为更具可读性。
【解决方案2】:

我不熟悉 es6 承诺,但是这里是我在 angularJS 中的基本操作,在概念上是相同的。

而不是返回 fetch 给出的承诺。您使用精心制作的,这使您能够在需要时解决它:

  • 如果它在缓存中,则立即
  • 当服务器响应准备好并被解析时。

这是我在 angluar 的承诺中处理缓存的方式

var cache = [..]//fetching cache from somewhere using an API 
var deferred = $q.defer();//build an empty promise;
if(cache.contains('myKey')){
    var data = cache.get('myKey');
    // wrap data in promise
    deferred.resolve(data);
}else{
     fetch(req).then(function(resp) {
         resp.json().then(function(json) {
             cache.put('myKey', json);
             deferred.resolve(json);//resolve promise
         });
     });
 }
 return deferred.promise;//return the promise object on which you will be able to call `then`

由于 es6 承诺与 Angular 的承诺非常接近,您应该能够调整您的代码以适应。

【讨论】:

  • 没错,如果您从为您管理缓存的 rlocalStorage 的某个 3rd 方库中检索它,甚至可能是缓存。
【解决方案3】:

现在我对 Promise 有了更好的理解,因此我有了一个更新的、更简洁的解决方案。毕竟我并没有完全通过.then() 进行链接,我意识到我之前的解决方案实际上使用了"The Deferred anti-pattern" aka http://taoofcode.net/promise-anti-patterns/

let cache = {};

function cachingFetchJSON(url) {
  if (!cache.hasOwnProperty(url)) {
    console.log('fetching from afar');
    cache[url] = fetch(url).then(resp => resp.json());
  } else {
    console.log('fetching from cache');
  }
  return cache[url];
}

for (let i = 0; i < 5; ++i) {
  cachingFetchJSON("//jsonplaceholder.typicode.com/albums/" + (Math.floor(Math.random() * 4) + 1))
    .then(val => console.log(val))
    .catch(c => console.error('caught', c));
}

这是我在发布此问题并从所有答案和 cmets 中学习后提出的原始解决方案:

我不只是缓存作为fetch() API 的一部分返回的promise,而是创建一个新promise,该promise 仅使用从响应正文中检索到的JSON 文本来解析。

function fetchJSON(url) {
  return new Promise((resolve, reject) => {
    console.log('fetching from afar');
    fetch(url)
      .then(resp => resp.json()
        .then(json => resolve(json)))
      .catch(reason => reject(reason));
  });
}

let cache = {};

function cachingFetchJSON(url) {
  if (!cache.hasOwnProperty(url))
    cache[url] = fetchJSON(url);
  else
    console.log('fetching from cache');
  return cache[url];
}

for (let i = 0; i < 5; ++i) {
  cachingFetchJSON("//jsonplaceholder.typicode.com/albums/" + (Math.floor(Math.random() * 4) + 1))
    .then(val => console.log(val))
    .catch(c => console.error('caught', c));
}

【讨论】:

    【解决方案4】:

    根据@Salva 的回答,我写了一个gist 使用lodash.memoizeresponse.clone()

    import memoize from 'lodash.memoize';
    
    const cache = memoize(req => {
        return fetch(req).then(response => {
            return response;
        });
    }, req => req.url);
    
    function cacheFetch(input, init) {
        return cache(new Request(input, init)).then(response => response.clone());
    }
    
    export default cacheFetch;
    

    【讨论】:

      猜你喜欢
      • 2018-02-19
      • 1970-01-01
      • 2020-02-27
      • 1970-01-01
      • 2018-05-21
      • 1970-01-01
      • 1970-01-01
      • 2019-12-19
      • 2017-06-11
      相关资源
      最近更新 更多