R a y B e l l i s@ r a y b e l l i sj Q u e r y U K – 2 0 1 3 / 0 4 / 1 91Persistent Memoization usingHTML5 indexedDB and ...
What is Memoization?2“Automatic caching of a pure function’s return value,so that a subsequent call with the same paramete...
Memoization Example Implementation3$.memoize = function(factory, ctx) {	var cache = {};	return function(key) {	if (!(key i...
Usage #1 – Expensive Calculations4// recursive Fibonacci – O(~1.6^n) !!	var fib = function(n) {	return (n < 2) ? n : fib(n...
Usage #2 – Repeated AJAX Calls5// AJAX function – returns a “Promise”	// expensive to call – may even cost real money!	fun...
Usage #2 – Repeated AJAX Calls6// AJAX function – returns a “Promise”	// expensive to call – may even cost real money!	fun...
HTML5 “indexedDB” to the Rescue7  Key/Value Store  Values may be Objects  localStorage only allows Strings  Databases ...
Database Versioning8$.indexedDB = function(dbname, store) {	var version; // initially undefined	(function retry() {	var re...
Callbacks…9$.indexedDB = function(dbname, store, callback) {	var version; // initially undefined	(function retry() {	var r...
… are so 2010!10  jQuery Promises  Introduced in jQuery 1.5  Incredibly useful for asynchronous event handling  Rich A...
Let’s ditch those callbacks!11$.indexedDB = function(dbname, store) {	var def = $.Deferred(); // I promise to return ...	v...
Usage12$.indexedDB("indexed", store).done(function(db) {	// use "db" here	...	});
Getting Back to Memoization13  One Database – avoids naming collisions  One object store per memoized function  Use Pro...
Code Walkthrough14$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed", store, keyP...
We need to return a function…15$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed"...
that returns a Promise…16$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed", stor...
and requires a DB connection…17$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed"...
that looks up the key…18$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed", store...
and if found, resolves the Promise…19$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("in...
otherwise, calls the original function…20$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB...
and $.when .done, stores it in the DB…21$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB(...
and asynchronously resolves the Promise22$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB...
if it can…23$.memoizeForever = function(factory, store, keyPath, ctx) {	var idb = $.indexedDB("indexed", store, keyPath);	...
Persistent Memoization Usage24// AJAX function – returns a “Promise”	// expensive to call – may even cost real money!	func...
Download25Source available at:https://gist.github.com/raybellis/5254306#file-jquery-memoize-js
Questions?26
Upcoming SlideShare
Loading in...5
×

Persistent Memoization with HTML5 indexedDB and jQuery Promises

1,177

Published on

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,177
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
4
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Persistent Memoization with HTML5 indexedDB and jQuery Promises

  1. 1. R a y B e l l i s@ r a y b e l l i sj Q u e r y U K – 2 0 1 3 / 0 4 / 1 91Persistent Memoization usingHTML5 indexedDB and Promises
  2. 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 ofrecalculating it.”Avoiding:  Expensive calculations  Repeated AJAX calls…
  3. 3. Memoization Example Implementation3$.memoize = function(factory, ctx) { var cache = {}; return function(key) { if (!(key in cache)) { cache[key] = factory.call(ctx, key); } return cache[key]; }; };
  4. 4. Usage #1 – Expensive Calculations4// 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 cacheinstead of being recalculated.The algorithm improves from O(~1.6^n) to O(n) for firstrun, and O(1) for previously calculated values of “n”.
  5. 5. Usage #2 – Repeated AJAX Calls5// 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 sameinput return the same promise, and thus the same result.
  6. 6. Usage #2 – Repeated AJAX Calls6// 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 sameinput return the same promise, and thus the same result.How could I cache results between sessions?
  7. 7. HTML5 “indexedDB” to the Rescue7  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. 8. Database Versioning8$.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. 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. 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. 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... };
  12. 12. Usage12$.indexedDB("indexed", store).done(function(db) { // use "db" here ... });
  13. 13. Getting Back to Memoization13  One Database – avoids naming collisions  One object store per memoized function  Use Promises for consistency with other jQueryasync operationsNo, I didn’t figure all this out in advance!
  14. 14. Code Walkthrough14$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  15. 15. We need to return a function…15$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  16. 16. that returns a Promise…16$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  17. 17. and requires a DB connection…17$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  18. 18. that looks up the key…18$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  19. 19. and if found, resolves the Promise…19$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  20. 20. otherwise, calls the original function…20$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  21. 21. and $.when .done, stores it in the DB…21$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  22. 22. and asynchronously resolves the Promise22$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  23. 23. if it can…23$.memoizeForever = function(factory, store, keyPath, ctx) { var idb = $.indexedDB("indexed", store, keyPath); return function(key) { var def = $.Deferred(); idb.done(function(db) { db.transaction(store).objectStore(store).get(key).onsuccess = function(ev) { if (typeof ev.target.result === "undefined") { $.when(factory.call(ctx, key)).done(function(data) { db.transaction(store, "readwrite").objectStore(store) .add(data).onsuccess = function() { def.resolve(data); }; }).fail(def.reject); } else { def.resolve(ev.target.result); } }; }); return def.promise(); }; };
  24. 24. Persistent Memoization Usage24// 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 previouslyobtained results, even between browser sessions!
  25. 25. Download25Source available at:https://gist.github.com/raybellis/5254306#file-jquery-memoize-js
  26. 26. Questions?26
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×