Your SlideShare is downloading. ×
Persistent Memoization with HTML5 indexedDB and jQuery Promises
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Persistent Memoization with HTML5 indexedDB and jQuery Promises

1,022
views

Published on

Published in: Technology

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

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

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 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. 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. 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. 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. 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. 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. 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. 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. 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... };
  • 12. Usage12$.indexedDB("indexed", store).done(function(db) { // use "db" here ... });
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Download25Source available at:https://gist.github.com/raybellis/5254306#file-jquery-memoize-js
  • 26. Questions?26