Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Persistent Memoization with HTML5 indexedDB and jQuery Promises
1. R a y B e l l i s
@ r a y b e l l i s
j Q u e r y U K – 2 0 1 3 / 0 4 / 1 9
1
Persistent Memoization using
HTML5 indexedDB and Promises
2. What is Memoization?
2
“Automatic caching of a pure function’s return value,
so that a subsequent call with the same parameter(s)
obtains the return value from a cache instead of
recalculating it.”
Avoiding:
Expensive calculations
Repeated AJAX calls…
3. Memoization Example Implementation
3
$.memoize = function(factory, ctx) {
var cache = {};
return function(key) {
if (!(key in cache)) {
cache[key] = factory.call(ctx, key);
}
return cache[key];
};
};
4. Usage #1 – Expensive Calculations
4
// recursive Fibonacci – O(~1.6^n) !!
var fib = function(n) {
return (n < 2) ? n : fib(n – 1) + fib(n – 2);
}
// wrap it
fib = $.memoize(fib);
The results of recursive calls are delivered from the cache
instead of being recalculated.
The algorithm improves from O(~1.6^n) to O(n) for first
run, and O(1) for previously calculated values of “n”.
5. Usage #2 – Repeated AJAX Calls
5
// AJAX function – returns a “Promise”
// expensive to call – may even cost real money!
function getGeo(ip) {
return $.getJSON(url, {ip: ip});
}
// create a wrapped version
var memoGeo = $.memoize(getGeo);
memoGeo(“192.168.1.1”).done(function(data) {
...
});
Repeated calls to the wrapped function for the same
input return the same promise, and thus the same result.
6. Usage #2 – Repeated AJAX Calls
6
// AJAX function – returns a “Promise”
// expensive to call – may even cost real money!
function getGeo(ip) {
return $.getJSON(url, {ip: ip});
}
// create a wrapped version
var memoGeo = $.memoize(getGeo);
memoGeo(“192.168.1.1”).done(function(data) {
...
});
Repeated calls to the wrapped function for the same
input return the same promise, and thus the same result.
How could I cache results between sessions?
7. HTML5 “indexedDB” to the Rescue
7
Key/Value Store
Values may be Objects
localStorage only allows Strings
Databases are origin specific (CORS)
Multiple tables (“object stores”) per Database
Asynchronous API
Sync API exists but may be deprecated by W3C
Schema changes require “Database Versioning”
8. Database Versioning
8
$.indexedDB = function(dbname, store) {
var version; // initially undefined
(function retry() {
var request;
if (typeof version === "undefined") {
request = indexedDB.open(dbname); // open latest version
} else {
request = indexedDB.open(dbname, version) // or open specific version number
}
request.onsuccess = function(ev) {
var db = ev.target.result;
if (!db.objectStoreNames.contains(store)) { // if the store is missing
version = db.version + 1; // increment version number
db.close(); // close the DB
retry(); // and open it again – NB: recursion!
} else {
// use the database here
...
}
};
request.onupgradeneeded = function(ev) {
var db = ev.target.result;
db.createObjectStore(store); // create new table
};
})(); // invoke immediately
}
9. Callbacks…
9
$.indexedDB = function(dbname, store, callback) {
var version; // initially undefined
(function retry() {
var request;
if (typeof version === "undefined") {
request = indexedDB.open(dbname); // open latest version
} else {
request = indexedDB.open(dbname, version) // or open specific version number
}
request.onsuccess = function(ev) {
var db = ev.target.result;
if (!db.objectStoreNames.contains(store)) { // if the store is missing
version = db.version + 1; // increment version number
db.close(); // close the DB
retry(); // and open it again – NB: recursion!
} else {
// use the database here
callback(db);
}
};
request.onupgradeneeded = function(ev) {
var db = ev.target.result;
db.createObjectStore(store); // create new table
};
})(); // invoke immediately
}
10. … are so 2010!
10
jQuery Promises
Introduced in jQuery 1.5
Incredibly useful for asynchronous event handling
Rich API
$.when()
.done()
.then()
etc
11. Let’s ditch those callbacks!
11
$.indexedDB = function(dbname, store) {
var def = $.Deferred(); // I promise to return ...
var version;
(function retry() {
var request;
if (typeof version === "undefined") {
request = indexedDB.open(dbname);
} else {
request = indexedDB.open(dbname, version);
}
request.onsuccess = function(ev) {
var db = ev.target.result;
if (!db.objectStoreNames.contains(store)) {
version = db.version + 1;
db.close();
retry();
} else {
// use the database here
def.resolve(db); // Tell the caller she can use the DB now
}
};
request.onupgradeneeded = function(ev) {
var db = ev.target.result;
db.createObjectStore(store);
};
})();
return def.promise(); // I really do promise...
};
13. Getting Back to Memoization
13
One Database – avoids naming collisions
One object store per memoized function
Use Promises for consistency with other jQuery
async operations
No, I didn’t figure all this out in advance!
24. Persistent Memoization Usage
24
// AJAX function – returns a “Promise”
// expensive to call – may even cost real money!
function getGeo(ip) {
return $.getJSON(url, {ip: ip});
}
// create a wrapped version
// Object store name is "geoip" and JSON path to key is "ip"
var memoGeo = $.memoizeForever(getGeo, "geoip", "ip");
memoGeo("192.168.1.1”).done(function(data) {
...
});
Now, repeated calls to the function return previously
obtained results, even between browser sessions!