javascriptjquerycachingpromisejquery-deferred

caching using jquery deferred versus promise


I currently have some code using jQuery Deferred and ajax to call remote api and get the data and put in localStorage and get data from localStorage.

This code seems buggy the first time it runs (i.e. it doesn't render the data until browser refresh) but it works perfectly after that until the cache expires and same problem again the first time it runs.

I want to rewrite the code using promise to modernize the codebase as well as tackle the bug. I am pretty new to JS promises and I wonder if there is any other improvements that can be made?

var btCalendarOps = {
  lifespan: 4*3600000,  //4 hours = 8*3600000
  def: $.Deferred(function(def){
        if (localStorage && localStorage.getItem('btCalData')) {
            def.resolve(JSON.parse(localStorage.getItem('btCalData')))
        }
        else {
            def.resolve({});
        }
  }).promise(),
  fetchData: function(){
    if (localStorage && (!localStorage.getItem('btCalData') 
      || !localStorage.getItem('btCalDataTimeSpan') 
      || parseInt($.now(), 10) - parseInt(localStorage.getItem('btCalDataTimeSpan'), 10) > this.lifespan)) 
    {
        console.log("localStorage refreshed with fresh API data");
        this.def = $.ajax({
            url: url,
            method: "GET",  
            dataType: 'json',
            success: function (data) {
                if (localStorage) {
                    localStorage.setItem('btCalData', JSON.stringify(data));
                }
             }
        }).then(function(){
            return JSON.parse(localStorage.getItem('btCalData'));
        });
        localStorage.setItem('btCalDataTimeSpan', $.now());
    }
    return this.def;
  }
}

//current usage

 btCalendarOps.fetchData().done(function (data) { 
        //render data as a list
    });

My new version:

const btCalendarOps = {
  lifespan: 4*3600000,  //4 hours = 8*3600000
  fetchData: function(){
    const cache = localStorage && localStorage.getItem('btCalData') 
            && localStorage.getItem('btCalDataTimeSpan')
            && parseInt($.now(), 10) - parseInt(localStorage.getItem('btCalDataTimeSpan'), 10) > this.lifespan 
            ? localStorage.getItem('btCalData') : null;

    // if the data is in the cache, return it.          
    if (cache){
      return Promise.resolve(JSON.parse(cache));
    } 

    // else get the data and store it.
    return Promise.resolve(
      fetch(url)
      .then((res) => res.json())
      .then((data) => {
        localStorage && localStorage.setItem('btCalData', JSON.stringify(data));localStorage.setItem('btCalDataTimeSpan', $.now());
        return data;
      })
    );

  }
}

Solution

  • You could use an async function:

    const btCalendarOps = {
        lifespan: 4*3600000,  //4 hours = 8*3600000
        async fetchData() {
            if (!localStorage) return {};
            if (!localStorage.getItem('btCalData')
                    || !localStorage.getItem('btCalDataTimeSpan') 
                    || Date.now() - localStorage.getItem('btCalDataTimeSpan') > this.lifespan) {
                console.log("localStorage to be refreshed with fresh API data");
                localStorage.setItem('btCalDataTimeSpan', Data.now());
                const data = await (await fetch(url)).json();
                localStorage.setItem('btCalData', JSON.stringify(data));
            }
            const json = localStorage.getItem('btCalData');
            return json ? JSON.parse(json) : {};
        }
    }
    

    You should no longer use done, but then (or async and await):

    btCalendarOps.fetchData().then(function (data) { 
        //render data as a list
    });