Skip to content Skip to sidebar Skip to footer

How Do I Handle Multiple Browser Scripts Making The Same Calls To The Back-end Service

I have a web page where different parts of it all need the same back-end data. Each is isolated, so they each end up eventually making the same calls to the back-end. What is th

Solution 1:

Just cache the result in the function making the call:

functioncache(promiseReturningFn){
    var cachedVal = null;  // start without cached valuefunctioncached(){
        if(cachedVal) return cachedVal; // prefer cached result
        cachedVal = promiseReturningFn.apply(this, arguments); // delegatereturn cachedVal; // after we saved it, return it
    }
    cached.flush = function(){ cachedVal = undefined; };
    return cached;
}

This has the caveat of failing for actual results that are null but otherwise it gets the job done nicely.

You can now cache any promise returning function - the version above only caches ignoring arguments - but you can construct a similar one that has a Map and caches based on different arguments too - but let's focus on your use case.

var getContactsCached = cache(getContacts);

getContactsCached();
getContactsCached();
getContactsCached(); // only one async call ever made

The cache method is actually not even related to promises - all it does is take a function and cache its result - you can use it for anything. In fact if you're using a library like underscore you can use _.memoize to do it for you already.

Solution 2:

If the desire is to reduce the number of unnecessary calls to the back-end, then hang on to the promise and while it's still unresolved, return it it for new calls rather than issuing another call to the back-end.

Here's a routine that converts an async function, one that returns a promise, into one that's only called while the promise is still unresolved.

var makeThrottleFunction = function (asyncFunction) {
  var currentPromiser = getPromise = function() {
    var promise = newPromise(function(resolve, reject) {
      asyncFunction().then(function(value) {
        resolve(value);
        currentPromiser = getPromise;
      }).catch(function(e) {
        reject(e);
        currentPromiser = getPromise;
      });
    });

    currentPromiser = function() {
      return promise;
    };

    return promise;
  }

  returnfunction () {
    returncurrentPromiser();
  };
};

In your routine, you can convert getContacts like so:

var getContacts = makeThrottleFunction(getContacts);

Or pass the entire body of the function directly.

Keep in mind that this will only work for parameterless calls to the back-end.

Example plunker code: http://plnkr.co/edit/4JTtHmFTZmiHugWNnlo9?p=preview

Solution 3:

Edit, Updated

Removed "nested" ternary pattern; added

  • a) dfd.err(), .catch() to handle Promise.reject(/* reason ? */)arguments passed to dfd.fn();
  • b) args === "" within dfd.process() to handle "": empty String passed as argument to dfd.fn()
  • c) substituted "chaining" .then() calls for then.apply(dfd.promise, [contactExists, getFirstContact])

Native Error() passed as argument:dfd.fn(new Error("error")) handled at global scope; dfd.fn() still returns dfd.promise. Could possibly adjust before or at dfd.process(), to return "early" at Error or pass Error to dfd.err() ; depending on requirement. Not addressed at js below.

Try

var dfd = {
  // set `active` : `false`"active": false,
  // set `promise`: `undefined`"promise": void 0,
  // process `arguments`, if any, passed to `dfd.fn`"process": function process(args) {
    //return`Function` call, `arguments`, 
    //or"current"`dfd.promise`;
    // were `args`:`arguments` passed ?
    // handle `""` empty `String` passed as `args`return args === "" || !!args
             // if`args`:`Function`, call `args` with `this`:`dfd`,
             //or, set `args` as `value`, `reason`
             // of "next"`dfd.promise`
             // return"next"`dfd.promise` 
           ? args instanceof Function && args.call(this) || args 
             // set `dfd.active`:`false`
             // when"current"`dfd.promise`:`Promise``fulfilled`,
             //return"current"`dfd.promise`
           : this.active = true && this.promise
  },
  // handle `fulfilled``Promise.reject(/* `reason` ? */)`,
  // passed as `args` to `dfd.fn`"err": function err(e) {
    // notify , log`reason`:`Promise.reject(/* `reason` ? */)`, if any,
    //or, log`undefined` , ifno`reason` passed: `Promise.reject()` 
    console.log("rejected `Promise` reason:", e || void 0);
  },
  //do stuff
  "fn": function fn(args /* , do other stuff */) {
    // set `_dfd` : `this` : `dfd` object
    var _dfd = this;
    // if "current" `dfd.promise`:`Promise` processing,
    // wait for `fulfilled` `dfd.promise`;
    // return `dfd.promise`
    _dfd.promise = !_dfd.active
                     // set, reset `dfd.promise`
                     // process call to `dfd.async`;
                     // `args`:`arguments` passed to `dfd.fn` ?,
                     // if `args` passed, are `args` `function` ?,
                     // if `args` `function`, call `args` with
                     // `this`:`dfd`; 
                     // or, return `args`
                   ? _dfd.process(args)
                     // if `_dfd.active`, `_dfd.promise` defined,
                     // return "current" `_dfd.promise`
                   : _dfd.promise.then(function(deferred) {
                        // `deferred`:`_dfd.promise`
                        // do stuff with `deferred`,
                        // do other stuff,
                        // return "current", "next" `deferred`
                        return deferred
                      })
                      // handle `args`:`fulfilled`,
                      // `Promise.reject(/* `reason` ? */)`
                      .catch(_dfd.err);
    return Promise.resolve(_dfd.promise).then(function(data) {
        // `data`:`undefined`, `_dfd.promise`
        // set `_dfd.active`:`false`,
        // return `value` of "current", "next" `_dfd.promise`
        _dfd.active = false;
        return data
      })
      // handle `fulfilled` `Promise.reject(/* `reason` ? */), 
      // if reaches here ?
      .catch(_dfd.err)
  }
};

functionlog() {
        var args = Array.prototype.slice.call(arguments).join(", ");
        console.log(args);
        var output = document.getElementById('output');
        output.innerHTML += args + "<br/>";
    };

    var dumpContacts = function () {
        log('Calling back-end to get contact list.');
        returnnewPromise(function (resolve, reject) {
            setTimeout(function () {
                log('New data received from back-end.');
                resolve(["Mary", "Frank", "Klaus"]);
            }, 3000);
        });
    };

    var getContacts = function () {
        return dfd.async().then(function (contacts) {
            for (var i = 0; i < contacts.length; i++) {
                log("Contact " + (i + 1) + ": " + contacts[i]);
            }
        });
    };

    var contactExists = function (contactName) {
        return dfd.async().then(function (contacts) {
            return contacts.indexOf(contactName) >= 0 ? true : false;
        });
    };

    var getFirstContact = function () {
        return dfd.async().then(function (contacts) {
            if (contacts.length > 0) {
                return contacts[0];
            }
        return contacts
    });
    };


    // Test:// Show all contacts
dfd.async(dumpContacts)
.then(getContacts)
.then.apply(dfd.promise, [
  // Does contact 'Jane' exist?contactExists("Jane").then(function (exists) {
    log("Contact 'Jane' exist: " + exists);
  })
  , getFirstContact().then(function (firstContact) {
    log("first contact: " + firstContact);
  })
]);

functionlog() {
  var args = Array.prototype.slice.call(arguments).join(", ");
  console.log(args);
  var output = document.getElementById('output');
  output.innerHTML += args + "<br/>";
  return output
};

var dfd = {
  "active": false,
  "promise": void0,
  "process": functionprocess(args) {
    return args === "" || !!args
           ? args instanceofFunction && args.call(this) || args 
           : this.active = true && this.promise
  },
  "err": functionerr(e) {
    console.log("rejected `Promise` reason:", e || void0);
  },
  "fn": functionfn(args) {
    var _dfd = this;
    _dfd.promise = !_dfd.active
                   ? _dfd.process(args)
                   : _dfd.promise.then(function(deferred) {
                       return deferred
                     })
                     .catch(_dfd.err);
    returnPromise.resolve(_dfd.promise).then(function(data) {
        _dfd.active = false;
        return data
      })
      .catch(_dfd.err)
  }
};

var dumpContacts = function() {
  log('Calling back-end to get contact list.');
  returnnewPromise(function(resolve, reject) {
    setTimeout(function() {
      log('New data received from back-end.');
      resolve(["Mary", "Frank", "Klaus"]);
    }, 3000);
  });
};

var getContacts = function() {
  return dfd.fn().then(function(contacts) {
    for (var i = 0; i < contacts.length; i++) {
      log("Contact " + (i + 1) + ": " + contacts[i]);
    }
  });
};

var contactExists = function(contactName) {
  return dfd.fn().then(function(contacts) {
    return contacts.indexOf(contactName) >= 0 ? true : false;
  });
};

var getFirstContact = function() {
  return dfd.fn().then(function(contacts) {
    if (contacts.length > 0) {
      return contacts[0];
    }
    return contacts
  });
};


// Test:// Show all contacts
dfd.fn(dumpContacts)
  .then(getContacts)
  .then(function() {
    // Does contact 'Jane' exist?returncontactExists("Jane").then(function(exists) {
      log("Contact 'Jane' exist: " + exists);
    })
  })
  .then(function() {
    returngetFirstContact().then(function(firstContact) {
      log("first contact: " + firstContact);
    })
  });
<body>
  Must use browser that supportes the Promises API, such as Chrome

  <divid='output'><br/></div><hr></body>

Post a Comment for "How Do I Handle Multiple Browser Scripts Making The Same Calls To The Back-end Service"