简体   繁体   中英

Create a simulated JavaScript Response from JSON data

I have an app that makes a lot of asynchronous fetch calls, some of which are identical.

I have a function that supersets fetch (say fetchPlus ), by creating a pseudo-unique identifier per request based on the arguments. That way, I can store the result in sessionStorage and access it.

function fetchCacheStore(hash) {
    const storeItem = 'fetch_' + hash;
    return {
        getCache: function () {
            return JSON.parse(sessionStorage.getItem(storeItem));
        },

        setCache: function (data) {
            sessionStorage.setItem(storeItem, JSON.stringify(data));
            setTimeout(function () { sessionStorage.removeItem(storeItem); }, 25); // Clear the cache item shortly after
        },
    };
}

function fetchPlus() {
    const stringHasher = function (s) { // Adapted from https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/22429679#comment94234739_7616484
        for (var i = h = 0; i < s.length; i++) {
            h = Math.imul(31, h) + s.charCodeAt(i) | 0;
        }
        return btoa(h);
    }

    let thisCallDetails = JSON.stringify(Array.prototype.slice.call(arguments).sort());
    let fetchCallHash = stringHasher(thisCallDetails);
    let fetchCache = fetchCacheStore(fetchCallHash);
    let fetchCacheGet = fetchCache.getCache();

    let promise;

    if (fetchCacheGet === null) { // The data is not cached
        promise = fetch(...arguments); // Create the fetch call
        promise.then(data => {
            data.close.json().then(content => {
                fetchCache.setCache(content);
            });
        }); // Store the result in the cache
    } else {
        let dataHeaders = { "status": 200, "Content-Type": "application/json" };
        promise = new Response(fetchCacheGet, dataHeaders); // Programatically create a Response
    }

    return promise;
}

Everything works well aside from the fact that when the data exists in sessionStorage , I am returning a JSON object directly, and not a Response , so in my code, when I do a call like so:

fetchPlus(url, params)
    .then(response => response.json())
    .then(data => …)

I end up with an error letting me know that I can't run json() on response .

The line promise = new Response(fetchCacheGet, dataHeaders); is probably incorrect, but I am not sure how to "reverse" the data into being the data spit out from the original fetch call. Maybe I'm missing something obvious. Or maybe this is all wrong.

I'm open to suggestions but this app is already set up so removing all the .then(response => response.json()) from the codebase is not an option.

Also, I am aware my code isn't the best in class, so do forgive me. Once again, open to suggestions as long as it's constructive.

I'd love help to make this work if anyone has a few minutes to spare.

UPDATE: Functioning code

Thanks to @AuxTaxo's answer below, I've solved my issue. For anyone interested, here is the updated code:

 function fetchCacheStore(hash) { const storeItem = 'fetch_' + hash; return { getCache: function () { return sessionStorage.getItem(storeItem); }, setCache: function (data) { sessionStorage.setItem(storeItem, data); setTimeout(function () { sessionStorage.removeItem(storeItem); }, 1000); // Clear the cache item after a short while }, }; } function fetchPlus() { const stringHasher = function (s) { // Adapted from https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/22429679#comment94234739_7616484 for (var i = h = 0; i < s.length; i++) { h = Math.imul(31, h) + s.charCodeAt(i) | 0; } return btoa(h); } let thisCallDetails = JSON.stringify(Array.prototype.slice.call(arguments).sort()); let fetchCallHash = stringHasher(thisCallDetails); let fetchCache = fetchCacheStore(fetchCallHash); let fetchCacheGet = fetchCache.getCache(); let promise; if (fetchCacheGet === null) { // The data is not cached promise = fetch(...arguments); // Create the fetch call promise.then(data => { data.clone().text().then(content => { fetchCache.setCache(content) // Store the result in the cache }); }); } else { let dataHeaders = { "status": 200, headers: { "Content-Type": "application/json" } }; // Programatically create a Response object, which works as a Promise promise = Promise.race([new Response(fetchCacheGet, dataHeaders)]); } return promise; } // Used as: `fetchPlus(url, params).then(response => response.json()).then(data => { /* … */ })`* 

I assume data.close.json().then is a typo of data.clone().json().then .

new Response() expects (among other options) a string, but you're passing it an object. The response body ends up being set to "[object Object]" , which .json() chokes on.

You can patch the problem by stringifying your object before passing it to the Response constructor, but a better solution would be to work with strings for as long as possible. Your Response bodies are strings, and Storage objects store strings, so store the result of response.text() in your cache instead of response.json() .

Also, you're only caching the result for 25 milliseconds , so sessionStorage 's advantage of keeping data across page refreshes doesn't seem useful. Just use a plain object as your cache. And dataHeaders should be { "status": 200, headers: { "Content-Type": "application/json" } } .

  function fetchPlus() {
    ...

    let promise;
    if (fetchCacheGet === null) { // The data is not cached
      promise = fetch(...arguments)
        .then(data => {
          return data.json()
            .then(content => {
              // read the response and cache
              fetchCache.setCache(content);
              const init = {
                'status': 200,
                'statusText': 'SuperSmashingGreat!'
              };
              return new Response(JSON.stringify(content), init); <-- recreate the response and it.
            });
        }); // Store the result in the cache
    } else {
      let dataHeaders = {
        'status': 200,
        'statusText': 'SuperSmashingGreat!'
      };
      promise = new Response(JSON.stringify(fetchCacheGet), dataHeaders); // Programatically create a Response
    }

    return promise;
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM