Lazy evaluation

kai zhu kaizhu256 at gmail.com
Fri Sep 1 04:36:52 UTC 2017


inline

> On Sep 1, 2017, at 1:03 AM, Isiah Meadows <isiahmeadows at gmail.com> wrote:
> 
> It'd solve a problem similarly to Kotlin's `by lazy { ... }` delegate,
> .NET's `System.Lazy<T>`, Swift's `lazy var`, among many other
> languages. It's very useful for lazy initialization [1], such as
> lazily setting up a database, requesting a resource, among other
> costly things. [2]
> 
> How often do you start out with a class like this, where you have an
> expensive resource you don't want to open right away?
> 
> ```js
> class Foo {
>    constructor() {
>        this._db = undefined
>    }
> 
>    _initDb() {
>        if (this._db) return this._db
>        return this._db = new Promise((resolve, reject) => {
>            // open a database connection
>            // set up whatever tables you need to
>            // etc.
>        })
>    }
> }
> ```

lazy db-initialization is over-engineering and unnecessary.  almost all applications i encounter can be designed more simply with explicit db-initialization during startup.  the only problem that arises is when the user tries to access the db before db-initialization completes.  my solution (for indexeddb) is to wrap every db-crud method with a deferred-callback that waits for db-initialization to complete (or a promise object as andreas mentioned).

https://kaizhu256.github.io/node-db-lite/build..master..travis-ci.org/apidoc.html#apidoc.element.db-lite.storageDefer <https://kaizhu256.github.io/node-db-lite/build..master..travis-ci.org/apidoc.html#apidoc.element.db-lite.storageDefer>
storageGetItem = function (key, onError) {
/*
 * this function will get the item with the given key from storage
 */
    defer({ action: 'getItem', key: key }, onError);
}
storageRemoveItem = function (key, onError) {
/*
 * this function will remove the item with the given key from storage
 */
    defer({ action: 'removeItem', key: key }, onError);
}
storageSetItem = function (key, value, onError) {
/*
 * this function will set the item with the given key and value to storage
 */
    defer({ action: 'setItem', key: key, value: value }, onError);
}
storageDefer = function (options, onError) {
/*
 * this function will defer options.action until storage is ready
 */
    var data, isDone, objectStore, onError2, request, tmp;
    onError = onError || function (error) {
        // validate no error occurred
        console.assert(!error, error);
    };
    if (!storage) {
        deferList.push(function () {
            defer(options, onError);
        });
        init();
        return;
    }
    switch (modeJs) {
    case 'browser':
        onError2 = function () {
            /* istanbul ignore next */
            if (isDone) {
                return;
            }
            isDone = true;
            onError(
                request && (request.error || request.transaction.error),
                data || request.result || ''
            );
        };
        switch (options.action) {
        case 'clear':
        case 'removeItem':
        case 'setItem':
            objectStore = storage
                .transaction(storageDir, 'readwrite')
                .objectStore(storageDir);
            break;
        default:
            objectStore = storage
                .transaction(storageDir, 'readonly')
                .objectStore(storageDir);
        }
        switch (options.action) {
        case 'clear':
            request = objectStore.clear();
            break;
        case 'getItem':
            request = objectStore.get(String(options.key));
            break;
        case 'keys':
            data = [];
            request = objectStore.openCursor();
            request.onsuccess = function () {
                if (!request.result) {
                    onError2();
                    return;
                }
                data.push(request.result.key);
                request.result.continue();
            };
            break;
        case 'length':
            request = objectStore.count();
            break;
        case 'removeItem':
            request = objectStore.delete(String(options.key));
            break;
        case 'setItem':
            request = objectStore.put(options.value, String(options.key));
            break;
        }
        ['onabort', 'onerror', 'onsuccess'].forEach(function (handler) {
            request[handler] = request[handler] || onError2;
        });
        // debug request
        local._debugStorageRequest = request;
        break;
    case 'node':
        switch (options.action) {
        case 'clear':
            child_process.spawnSync('rm -f ' + storage + '/*', {
                shell: true,
                stdio: ['ignore', 1, 2]
            });
            setTimeout(onError);
            break;
        case 'getItem':
            fs.readFile(
                storage + '/' + encodeURIComponent(String(options.key)),
                'utf8',
                // ignore error
                function (error, data) {
                    onError(error && null, data || '');
                }
            );
            break;
        case 'keys':
            fs.readdir(storage, function (error, data) {
                onError(error, data && data.map(decodeURIComponent));
            });
            break;
        case 'length':
            fs.readdir(storage, function (error, data) {
                onError(error, data && data.length);
            });
            break;
        case 'removeItem':
            fs.unlink(
                storage + '/' + encodeURIComponent(String(options.key)),
                // ignore error
                function () {
                    onError();
                }
            );
            break;
        case 'setItem':
            tmp = os.tmpdir() + '/' + Date.now() + Math.random();
            // save to tmp
            fs.writeFile(tmp, options.value, function (error) {
                // validate no error occurred
                console.assert(!error, error);
                // rename tmp to key
                fs.rename(
                    tmp,
                    storage + '/' + encodeURIComponent(String(options.key)),
                    onError
                );
            }); ...







> Or maybe, a large lookup table that takes a while to build, and might
> not even be used, so you don't want to do it on load?
> 
> ```js
> var table
> 
> function initTable() {
>    if (table) return
>    table = new Array(10000)
>    // do some expensive calculations
> }
> ```

this is a textbook-example for using memoization as alexander mentioned.  here's a real-world memoized file-server solution
https://kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.middlewareFileServer <https://kaizhu256.github.io/node-utility2/build..master..travis-ci.org/apidoc.html#apidoc.element.utility2.middlewareFileServer>
middlewareFileServer = function (request, response, nextMiddleware) {
/*
 * this function will run the middleware that will serve files
 */
    if (request.method !== 'GET' || local.modeJs === 'browser') {
        nextMiddleware();
        return;
    }
    request.urlFile = (process.cwd() + request.urlParsed.pathname
        // security - disable parent directory lookup
        .replace((/.*\/\.\.\//g), '/'))
        // replace trailing '/' with '/index.html'
        .replace((/\/$/), '/index.html');
    // serve file from cache
    local.taskCreateCached({
        cacheDict: 'middlewareFileServer',
        key: request.urlFile
    // run background-task to re-cache file
    }, function (onError) {
        local.fs.readFile(request.urlFile, function (error, data) {
            onError(error, data && local.base64FromBuffer(data));
        });
    }, function (error, data) {
        // default to nextMiddleware
        if (error) {
            nextMiddleware();
            return;
        }
        // init response-header content-type
        request.urlParsed.contentType = (/\.[^\.]*$/).exec(request.urlParsed.pathname);
        request.urlParsed.contentType = local.contentTypeDict[
            request.urlParsed.contentType && request.urlParsed.contentType[0]
        ];
        local.serverRespondHeadSet(request, response, null, {
            'Content-Type': request.urlParsed.contentType
        });
        // serve file from cache
        response.end(local.base64ToBuffer(data));
    });
}
taskCreateCached = function (options, onTask, onError) {
/*
 * this function will
 * 1. if cache-hit, then call onError with cacheValue
 * 2. run onTask in background
 * 3. save onTask's result to cache
 * 4. if cache-miss, then call onError with onTask's result
 */
    local.onNext(options, function (error, data) {
        switch (options.modeNext) {
        //  1. if cache-hit, then call onError with cacheValue
        case 1:
            // read cacheValue from memory-cache
            local.cacheDict[options.cacheDict] = local.cacheDict[options.cacheDict] ||
                {};
            options.cacheValue = local.cacheDict[options.cacheDict][options.key];
            if (options.cacheValue) {
                // call onError with cacheValue
                options.modeCacheHit = true;
                onError(null, JSON.parse(options.cacheValue));
                if (!options.modeCacheUpdate) {
                    break;
                }
            }
            // run background-task with lower priority for cache-hit
            setTimeout(options.onNext, options.modeCacheHit && options.cacheTtl);
            break;
        // 2. run onTask in background
        case 2:
            local.taskCreate(options, onTask, options.onNext);
            break;
        default:
            // 3. save onTask's result to cache
            // JSON.stringify data to prevent side-effects on cache
            options.cacheValue = JSON.stringify(data);
            if (!error && options.cacheValue) {
                local.cacheDict[options.cacheDict][options.key] = options.cacheValue;
            }
            // 4. if cache-miss, then call onError with onTask's result
            if (!options.modeCacheHit) {
                onError(error, options.cacheValue && JSON.parse(options.cacheValue));
            }
            (options.onCacheWrite || local.nop)();
            break;
        }
    });
    options.modeNext = 0;
    options.onNext();
}


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170901/2a8720f8/attachment-0001.html>


More information about the es-discuss mailing list