admin管理员组文章数量:1323503
Sometimes you want to use the same promised object in multiple times and places. Such as a promise for an AJAX resource from a URL.
It's difficult to cache the object because you don't know when it will asynchronously be avaible.
That's OK because you can call .then()
multiple times on a single promise so you cache it the first time you need it and access the cache the subsequent times. And when it's available all the .then
s will be carried out.
But when the promise is from the new fetch API it doesn't appear to be so straightforward... because a second promise is involved.
fetch()
itself returns a promise and when it's fulfilled you must access the HTTP response's body via a second promise using a method such as .blob()
or .json()
. These are part of the "Body mixin".
Vanilla example of how to use fetch()
:
fetch(url).then(function(resp) {
return resp.json().then(function(json) {
// use the JSON
});
});
The problems is that even though you can call .then()
multiple times you can't call the functions such as .blob()
and .json()
multiple times on a single response.
It seems then that the result of .json()
etc would also need to be cached, but that ought to be more difficult since it will only be available asynchronously. The resource could be requested twice before the two asynch promises resolve - a race condition.
Am I missing something? Is there an easy way to cache fetch's promise's JSON etc than I can't see? I can't seem to find this discussed on the net anywhere.
Sometimes you want to use the same promised object in multiple times and places. Such as a promise for an AJAX resource from a URL.
It's difficult to cache the object because you don't know when it will asynchronously be avaible.
That's OK because you can call .then()
multiple times on a single promise so you cache it the first time you need it and access the cache the subsequent times. And when it's available all the .then
s will be carried out.
But when the promise is from the new fetch API it doesn't appear to be so straightforward... because a second promise is involved.
fetch()
itself returns a promise and when it's fulfilled you must access the HTTP response's body via a second promise using a method such as .blob()
or .json()
. These are part of the "Body mixin".
Vanilla example of how to use fetch()
:
fetch(url).then(function(resp) {
return resp.json().then(function(json) {
// use the JSON
});
});
The problems is that even though you can call .then()
multiple times you can't call the functions such as .blob()
and .json()
multiple times on a single response.
It seems then that the result of .json()
etc would also need to be cached, but that ought to be more difficult since it will only be available asynchronously. The resource could be requested twice before the two asynch promises resolve - a race condition.
Am I missing something? Is there an easy way to cache fetch's promise's JSON etc than I can't see? I can't seem to find this discussed on the net anywhere.
Share edited Jun 17, 2016 at 12:14 hippietrail asked Jun 15, 2016 at 14:11 hippietrailhippietrail 17k21 gold badges109 silver badges179 bronze badges 6- Is there a reason you can't use a variable to store that? – ReX357 Commented Jun 15, 2016 at 14:14
- Where and when would the variable go to be accessible considering the asynchronicity? – hippietrail Commented Jun 15, 2016 at 14:25
- It is unclear the scope in which you need to use this variable. Inside your then block after the fetch call, you could define a variable above the return statement and assign it in the first then block of said return statement. Then a subsequent return statement inside the the same fetch block could access the contents of that variable. But once again, your question was unclear as to when those cache results need to be accessed. If it's beyond the fetch request and any subsequent chaining, then please specify so. – ReX357 Commented Jun 15, 2016 at 22:16
- Well my real code is convoluted involving at least two loops of asynchronous fetch calls to retrieve values that will then be filtered and cross referenced against each other. That's why I was wanting to store values in a cache. Of course one could consider a cache object to be a variable. But one loop will be writing to it and another will be reading from it and both asynchronous. My question attempts to address the general problem rather than code review my specific project (-: – hippietrail Commented Jun 16, 2016 at 4:25
- I think you might get a better answer if you explained exactly what you want to happen. 2 loops of asynchronous fetch calls? Are these 2 loops running in parallel? And when is the filtering and the 'cross referencing' happening? More details would really help. The only thing I can gather from your problem right now might be that you want to use Promise.all and wait for both loops to be done and then do your filtering/cross referencing synchronously but again what you are trying to acplish seems very vague. – ReX357 Commented Jun 17, 2016 at 20:46
4 Answers
Reset to default 3You can not call .json()
or .text()
multiple times on a response because any of these methods consumes the body but you can copy the response before using it so you can simply cache the promise but instead of doing:
cachedFetch.then(response => response.json()).then(console.log);
Do:
cachedFetch.then(response => response.clone().json()).then(console.log);
Or wrap your fetch
call inside a function that always returns a respone clone:
doFetch().then(response => response.json()).then(console.log);
Where:
var _cached;
function doFetch() {
if (!_cached) {
_cached = fetch('/something');
}
return _cached.then(r => r.clone());
}
I have an updated and much more concise solution now that I understand promises a bit better. I didn't fully grok chaining via .then()
after all and I realized my previous solution actually used "The Deferred anti-pattern" aka http://taoofcode/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./albums/" + (Math.floor(Math.random() * 4) + 1))
.then(val => console.log(val))
.catch(c => console.error('caught', c));
}
And here's the original solution I came up with after posting this question and learning from all the answers and ments:
Rather than just cache the promises returned as part of the fetch()
API I create a new promise which is resolved only with the JSON text retrieved from the response body.
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./albums/" + (Math.floor(Math.random() * 4) + 1))
.then(val => console.log(val))
.catch(c => console.error('caught', c));
}
I'm not familiar with es6 promise, however here is the basics how i do it in angularJS which is the same conceptually.
Instead of returning the promise given by fetch. You use a crafted one, this gives you the power to resolve it when you want :
- Immediately if it's in the cache
- When the server response is ready and parsed.
Here is how i handle caching in angluar's 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`
Since es6 promise are really close of angular's one you should be able to adapt your code to fit.
Based on @Salva answer, I wrote a gist that uses lodash.memoize and response.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;
本文标签: javascriptHow to go about caching promises when working with the new fetch APIStack Overflow
版权声明:本文标题:javascript - How to go about caching promises when working with the new fetch API? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742141448a2422597.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论