How Do I Handle Multiple Browser Scripts Making The Same Calls To The Back-end Service
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 handlePromise.reject(/* reason ? */)
arguments
passed todfd.fn()
; - b)
args === ""
withindfd.process()
to handle""
: emptyString
passed asargument
todfd.fn()
- c) substituted "chaining"
.then()
calls forthen.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"