使用新的 fetch API 时如何缓存 promises?

How to go about caching promises when working with the new fetch API?

有时您想在多个时间和地点使用相同的承诺对象。例如来自 URL.

的 AJAX 资源的承诺

很难缓存对象,因为您不知道它何时会异步可用。

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

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

fetch() 本身 returns 一个承诺,当它实现时,您必须使用诸如 .blob() or .json(). These are part of the "Body mixin".

之类的方法通过第二个承诺访问 HTTP 响应的主体

关于如何使用 fetch() 的普通示例:

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

问题在于,即使您可以多次调用 .then(),您 也不能 调用 .blob() 和 [=16= 等函数] 一次响应多次。

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

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

我不熟悉 es6 promise,但是这是我在 angularJS 中的基本操作方法,在概念上是相同的。

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

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

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

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 promise 与 angular 的 promise 非常接近,你应该能够调整你的代码以适应。

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

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

做:

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

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

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

其中:

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

我有一个更新的 更简洁 的解决方案,现在我对 promises 有了更好的理解。毕竟我没有完全通过 .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));
}

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

我创建了一个新的承诺,而不是仅仅缓存作为 fetch() API 的一部分返回的承诺,它仅通过从响应正文中检索到的 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));
}

根据@Salva 的回答,我写了 gist that uses 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;