node package manager

simple-file-cache

In-memory cache to improve performance of repeated reads when using FileReader in the browser

simple-file-cache ReadMe

Introduction

simple-file-cache is a simple in-memory cache to improve performance when reading files using the HTML5 FileReader API. Performance improvements can be observed on all browsers, but are most dramatic with Chrome and Safari. For instance, these times to complete a moderately complex workload were observed:

  • on desktop Chrome:
    • 40 seconds (without simple-file-cache)
    • 3 seconds (with simple-file-cache)
  • on mobile Chrome (Samsung Galaxy S II):
    • 200 seconds (without simple-file-cache)
    • 20 seconds (with simple-file-cache)
  • on desktop Safari:
    • 15 seconds (without simple-file-cache)
    • 1 second (with simple-file-cache)
  • on desktop Firefox:
    • 20 seconds (without simple-file-cache)
    • 2 seconds (with simple-file-cache)

(lower times are better, obviously)

Dependencies

A browser environment with the asynchronous FileReader API.

Usage

simple-file-cache needs to be included in the web page before the code that wants to use it (it should be possible to use browserify to have it work with require() instead, though I have not tried).

Its entry point is the simpleFileCacheLoader() function, but its dependencies need to be injected, which is best done by using this sample code:

var prefixator = function (apiobject, memberFuncName, prefixArray) {
    var result = undefined;
    var daFunc = apiobject[memberFuncName];
    if (daFunc) {
        return {
            prefix: '',
            obj: daFunc
        };
    }

    var capMFC = memberFuncName.charAt(0).toUpperCase() + memberFuncName.slice(1);

    try {
        prefixArray.forEach(function (element) {
            daFunc = apiobject[element + capMFC];
            if (daFunc) {
                result = {
                    prefix: element,
                    obj: daFunc
                };
                throw {
                    name: 'BreakFromForEachException',
                    message: 'Exception to end array enumeration prematurely'
                };
            }
        });
    } catch (e) {
        if (e.name !== 'BreakFromForEachException') { // rethrow
            throw e;
        }
        // otherwise, just swallow our exception
    }

    // if none was found, this will return undefined
    return result;
};

var installProperSlice = function (obj) {
    return obj;
};

(function () {
    var temp = function (obj) {
        var cached_imp = prefixator(obj, "slice", ["webkit", "moz"]).obj;
        obj.properSlice = function () {
            return installProperSlice(cached_imp.apply(obj, arguments));
        };
        return obj;
    };

    installProperSlice = temp;
}());

var simpleFileCache = simpleFileCacheLoader({
        EMPTY: 0,
        LOADING: 1,
        DONE: 2,
        "blob": function (param1, param2) {
    var new_blob = null;
    // utterly silly impedance mismatch (I tried Function.prototype.bind.apply without success: TypeError)
    if (param2 === undefined) {
        new_blob = new window.Blob(param1);
    } else {
        new_blob = new window.Blob(param1, param2);
    }
    return installProperSlice(new_blob);
},
        "fileReader": function () {
    return new window.FileReader();
},
        "byteArray": function (param) {
    return new window.Uint8Array(param);
}});

After that, all functionality is accessed through the simpleFileCache object:

  • simpleFileCache.createCachedCopy(blob)
    • parameters:
      • blob: an instance of window.Blob
    • result: a CachedBlob, with the same contents as the parameter, that can be used with the functions from simpleFileCache.api
  • simpleFileCache.createUncachedCopy(cachedBlob)
    • parameters:
      • pseudoBlob: a CachedBlob object generated using the functions from simpleFileCache.api
    • result: an instance of window.Blob with the same contents as the parameter
  • simpleFileCache.api.blob(): same as window.Blob, except it has a properSlice function instead of slice or webkitSlice, etc., and it operates exclusively on CachedBlob, CachedUInt8Array, etc. objects
  • simpleFileCache.api.fileReader(): same as window.FileReader, except it operates exclusively on CachedBlob, CachedUInt8Array, etc. objects
  • simpleFileCache.api.byteArray(): same as window.Uint8Array, except it operates exclusively on CachedBlob, CachedUInt8Array, etc. objects

The principle is that simpleFileCache.api is meant to be passed to a module that itself expects FileReader to be injected, such that the module does not even have to know it is using the simple-file-cache API. You can see how this is used in the JPS project, which you can think of as the simple-file-cache example usage code.

In particular, it allows simple-file-cache to be easily taken out: just inject the FileReader API instead of the simple-file-cache API, and you're back to using FileReader directly. This will be useful the day browsers will finally fix this performance issue and render simple-file-cache unnecessary.

Note that you cannot mix the FileReader API with the simpleFileCache.api: for instance, you cannot use an instance generated by window.FileReader to read a CachedBlob: you must get a Blob from it first, using simpleFileCache.createUncachedCopy(cachedBlob).

Tests

Open test_harness/main.html with any browser (no support for FileReader is even required) to execute the self-tests.

Reporting Bugs

Email me if you find anything.

Support

simple-file-cache is free to use and modify (under the terms of the BSD license). If you find it useful, I request that you consider donating to the ACLU and the UNHCR, however.