Çalışanlar için eşzamanlı FileSystem API'si

Giriş

HTML5 FileSystem API ve Web Workers kendi açısından son derece güçlüdür. FileSystem API nihayet web uygulamalarına hiyerarşik depolama ve dosya G/Ç'si getirirken Çalışanlar gerçek eşzamansız "çoklu iş parçacığı"nı JavaScript'e taşır. Ancak bu API'ları bir arada kullandığınızda gerçekten ilgi çekici uygulamalar oluşturabilirsiniz.

Bu eğiticide, bir Web Çalışanı içinde HTML5 Dosya Sistemi'nden yararlanmak için kılavuz ve kod örnekleri sağlanmaktadır. Her iki API'nin de çalışma bilgisine sahip olduğu varsayılır. Bu API'lerle ilgili ayrıntılı bilgi edinmeye henüz hazır değilseniz veya bu API'ler hakkında daha fazla bilgi edinmek istiyorsanız temel bilgileri içeren iki harika eğiticiyi okuyun: Dosya Sistemi API'larını Keşfetme ve Web Çalışanlarıyla İlgili Temel Bilgiler.

Eşzamanlı ve eşzamansız API'ler

Eşzamansız JavaScript API'lerinin kullanımı zor olabilir. Boyutları büyük. Bunlar karmaşıktır. Ancak en can sıkıcı durum, sorunların ters gitmesini sağlayacak pek çok fırsat sunmasıdır. Son olarak uğraşmak isteyeceğiniz şey, zaten eşzamansız olan bir dünyada (Çalışanlar) karmaşık, eşzamansız bir API'yi (FileSystem) kullanarak katman oluşturmaktır. Bununla birlikte, FileSystem API'nin eşzamanlı bir sürüm tanımlayarak Web İşçileri'nin yaşadığı sıkıntıyı hafifletmesi de iyi bir haberimiz var.

Çoğu durumda, eşzamanlı API ile eşzamansız API aynıdır. Yöntemler, özellikler, özellikler ve işlevsellik tanıdık gelecektir. Önemli sapmalar şunlardır:

  • Eşzamansız API, yalnızca Web İşçisi bağlamında kullanılabilir. Eşzamansız API ise bir Çalışanın içinde ve dışında kullanılabilir.
  • Geri çağırma işlevi sona erdi. API yöntemleri artık değer döndürüyor.
  • Pencere nesnesindeki genel yöntemler (requestFileSystem() ve resolveLocalFileSystemURL()) requestFileSystemSync() ve resolveLocalFileSystemSyncURL() olur.

Bu istisnalar dışında API'ler aynıdır. Tamam, başlayalım.

Dosya sistemi isteme

Bir web uygulaması, bir Web Çalışanı içinden LocalFileSystemSync nesnesi isteyerek eşzamanlı dosya sistemine erişim elde eder. requestFileSystemSync(), Çalışanın global kapsamına tabidir:

var fs = requestFileSystemSync(TEMPORARY, 1024*1024 /*1MB*/);

Artık eşzamanlı API'yi kullandığımıza göre, başarı ve hata geri çağırması olmamasının yanı sıra yeni döndürülen değere dikkat edin.

Normal FileSystem API'sında olduğu gibi yöntemlere şu anda ön ek eklenir:

self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
                                 self.requestFileSystemSync;

Kotayla başa çıkma

Şu anda Çalışan bağlamında PERSISTENT kotası istemek mümkün değildir. Kota sorunlarıyla Çalışanların dışındaki kişilerle ilgilenmenizi öneririm. İşlem şöyle görünebilir:

  1. Worker.js: QUOTA_EXCEED_ERR hatalarının yakalanması için tüm FileSystem API kodlarını try/catch ile sarmalayın.
  2. Worker.js: Bir QUOTA_EXCEED_ERR yakalarsanız, ana uygulamaya geri postMessage('get me more quota') gönderin.
  3. ana uygulama: 2 numara alındığında window.webkitStorageInfo.requestQuota() dansını gerçekleştirin.
  4. ana uygulama: Kullanıcı daha fazla kota verdikten sonra postMessage('resume writes') ürününü ek depolama alanı hakkında bilgilendirmek için çalışana geri gönderin.

Bu oldukça ayrıntılı bir çözümdür ama işe yarayacaktır. PERSISTENT depolama alanını FileSystem API ile kullanma hakkında daha fazla bilgi için kota isteme sayfasına göz atın.

Dosyalar ve dizinlerle çalışma

Eşzamanlı getFile() ve getDirectory() sürümleri sırasıyla FileEntrySync ve DirectoryEntrySync döndürür.

Örneğin, aşağıdaki kod kök dizinde "log.txt" adında boş bir dosya oluşturur.

var fileEntry = fs.root.getFile('log.txt', {create: true});

Aşağıdaki komut, kök klasörde yeni bir dizin oluşturur.

var dirEntry = fs.root.getDirectory('mydir', {create: true});

Hataları işleme

Web Worker kodunda hiç hata ayıklamak zorunda kalmadıysanız sizi kıskandırıyorum! Neyin yanlış gittiğini anlamak gerçekten çok zor olabilir.

Eşzamanlı dünyada hata geri çağırmalarının olmaması, sorunlarla ilgilenmeyi olması gerektiğinden daha zor hale getirir. Web İşçisi kodunda hata ayıklamanın genel karmaşıklığını eklersek, bir an önce hayal kırıklığına uğrarsınız. İşinizi kolaylaştırabilecek şeylerden biri, tüm ilgili Çalışan kodlarınızı bir dene-yakala ile sarmalamaktır. Daha sonra, herhangi bir hata oluşursa postMessage() kullanarak hatayı ana uygulamaya yönlendirin:

function onError(e) {
    postMessage('ERROR: ' + e.toString());
}

try {
    // Error thrown if "log.txt" already exists.
    var fileEntry = fs.root.getFile('log.txt', {create: true, exclusive: true});
} catch (e) {
    onError(e);
}

Dosyalar, Blob'lar ve ArrayBuffer'ların arasında geçiş yapma

Web İşçileri sahneye ilk geldiğinde, yalnızca postMessage() içinde dize verilerinin gönderilmesine izin veriyordu. Daha sonra tarayıcılar seri hale getirilebilir verileri kabul etmeye başladı. Bu, bir JSON nesnesinin geçirilmesinin mümkün olduğu anlamına geliyordu. Ancak son zamanlarda Chrome gibi bazı tarayıcılar, yapılandırılmış klon algoritması kullanılarak postMessage() üzerinden aktarılan daha karmaşık veri türlerini kabul etmektedir.

Bu tam olarak ne anlama geliyor? Yani ana uygulama ile Worker iş parçacığı arasında ikili veri aktarımının çok daha kolay olduğu anlamına gelir. Çalışanlar için yapılandırılmış klonlamayı destekleyen tarayıcılar, Yazılan Diziler, ArrayBuffer, File veya Blob öğelerini Çalışanlara iletmenize olanak tanır. Veriler hâlâ kopya olsa da File iletme olanağı, dosyayı postMessage() içine geçirmeden önce base64 işlemeyi gerektiren eski yaklaşıma göre daha iyi bir performans avantajı anlamına gelir.

Aşağıdaki örnekte, kullanıcı tarafından seçilen bir dosya listesi özel bir Çalışana iletilir. Çalışan, dosya listesinden geçer (döndürülen verilerin gösterilmesi basit bir işlemdir, aslında bir FileList'dir) ve ana uygulama her dosyayı ArrayBuffer olarak okur.

Örnekte ayrıca, Web İşçilerinin Temelleri bölümünde açıklanan satır içi Web İşçisi tekniğinin geliştirilmiş bir sürümü kullanılmaktadır.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="chrome=1">
    <title>Passing a FileList to a Worker</title>
    <script type="javascript/worker" id="fileListWorker">
    self.onmessage = function(e) {
    // TODO: do something interesting with the files.
    postMessage(e.data); // Pass through.
    };
    </script>
</head>
<body>
</body>

<input type="file" multiple>

<script>
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
    var files = this.files;
    loadInlineWorker('#fileListWorker', function(worker) {

    // Setup handler to process messages from the worker.
    worker.onmessage = function(e) {

        // Read each file aysnc. as an array buffer.
        for (var i = 0, file; file = files[i]; ++i) {
        var reader = new FileReader();
        reader.onload = function(e) {
            console.log(this.result); // this.result is the read file as an ArrayBuffer.
        };
        reader.onerror = function(e) {
            console.log(e);
        };
        reader.readAsArrayBuffer(file);
        }

    };

    worker.postMessage(files);
    });
}, false);


function loadInlineWorker(selector, callback) {
    window.URL = window.URL || window.webkitURL || null;

    var script = document.querySelector(selector);
    if (script.type === 'javascript/worker') {
    var blob = new Blob([script.textContent]);
    callback(new Worker(window.URL.createObjectURL(blob));
    }
}
</script>
</html>

Bir Çalışanda dosya okuma

Çalışanlarda eşzamansız FileReader API ile dosyaları okumak sorunsuz bir şekilde kullanılabilir. Ancak daha iyi bir yol var. Çalışanlarda, dosyaların okunmasını kolaylaştıran eşzamanlı bir API (FileReaderSync) vardır:

Ana uygulama:

<!DOCTYPE html>
<html>
<head>
    <title>Using FileReaderSync Example</title>
    <style>
    #error { color: red; }
    </style>
</head>
<body>
<input type="file" multiple />
<output id="error"></output>
<script>
    var worker = new Worker('worker.js');

    worker.onmessage = function(e) {
    console.log(e.data); // e.data should be an array of ArrayBuffers.
    };

    worker.onerror = function(e) {
    document.querySelector('#error').textContent = [
        'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join('');
    };

    document.querySelector('input[type="file"]').addEventListener('change', function(e) {
    worker.postMessage(this.files);
    }, false);
</script>
</body>
</html>

worker.js

self.addEventListener('message', function(e) {
    var files = e.data;
    var buffers = [];

    // Read each file synchronously as an ArrayBuffer and
    // stash it in a global array to return to the main app.
    [].forEach.call(files, function(file) {
    var reader = new FileReaderSync();
    buffers.push(reader.readAsArrayBuffer(file));
    });

    postMessage(buffers);
}, false);

Beklendiği gibi, eşzamanlı FileReader ile geri çağırmalar kaldırıldı. Bu, dosyaları okurken geri çağırma (callback) iç içe yerleştirme miktarını basitleştirir. Bunun yerine, readAs* yöntemleri okunan dosyayı döndürür.

Örnek: Tüm girişleri getirme

Bazı durumlarda, zaman uyumlu API belirli görevler için çok daha anlaşılırdır. Geri çağırmanın daha az olması hoş bir şeydir ve kesinlikle her şeyi daha okunabilir hale getirir. Senkronize API'nin asıl dezavantajı, Çalışanların sınırlamalarından kaynaklanıyor.

Güvenlik nedeniyle, çağrı yapan uygulama ile Web Çalışanı iş parçacığı arasındaki veriler hiçbir zaman paylaşılmaz. postMessage() çağrıldığında, veriler her zaman Çalışana ve Çalışandan kopyalanır. Bu nedenle, her veri türü iletilemez.

Maalesef FileEntrySync ve DirectoryEntrySync şu anda kabul edilen türler arasında değildir. Peki, arama uygulamasına girişleri nasıl geri alabilirsiniz? Bu sınırlamayı atlatmanın bir yolu, giriş listesi yerine filesystem: URL'ler listesi döndürmektir. filesystem: URL'ler sadece dizeden oluşur, bu nedenle geçişleri son derece kolaydır. Dahası, ana uygulamaya resolveLocalFileSystemURL() kullanılarak giriş yapmaya da çözümlenebilir. Böylece tekrar FileEntrySync/DirectoryEntrySync nesnesine sahip olursunuz.

Ana uygulama:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Listing filesystem entries using the synchronous API</title>
</head>
<body>
<script>
    window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL ||
                                        window.webkitResolveLocalFileSystemURL;

    var worker = new Worker('worker.js');
    worker.onmessage = function(e) {
    var urls = e.data.entries;
    urls.forEach(function(url, i) {
        window.resolveLocalFileSystemURL(url, function(fileEntry) {
        console.log(fileEntry.name); // Print out file's name.
        });
    });
    };

    worker.postMessage({'cmd': 'list'});
</script>
</body>
</html>

worker.js

self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
                                self.requestFileSystemSync;

var paths = []; // Global to hold the list of entry filesystem URLs.

function getAllEntries(dirReader) {
    var entries = dirReader.readEntries();

    for (var i = 0, entry; entry = entries[i]; ++i) {
    paths.push(entry.toURL()); // Stash this entry's filesystem: URL.

    // If this is a directory, we have more traversing to do.
    if (entry.isDirectory) {
        getAllEntries(entry.createReader());
    }
    }
}

function onError(e) {
    postMessage('ERROR: ' + e.toString()); // Forward the error to main app.
}

self.onmessage = function(e) {
    var data = e.data;

    // Ignore everything else except our 'list' command.
    if (!data.cmd || data.cmd != 'list') {
    return;
    }

    try {
    var fs = requestFileSystemSync(TEMPORARY, 1024*1024 /*1MB*/);

    getAllEntries(fs.root.createReader());

    self.postMessage({entries: paths});
    } catch (e) {
    onError(e);
    }
};

Örnek: XHR2 kullanarak dosya indirme

Çalışanlar için yaygın bir kullanım alanı, XHR2 kullanarak bir grup dosyayı indirmek ve bu dosyaları HTML5 Dosya Sistemi'ne yazmaktır. Bu, Worker iş parçacığı için mükemmel bir görevdir.

Aşağıdaki örnek yalnızca bir dosya getirir ve yazar, ancak bir dosya grubunu indirmek için dosyayı genişleterek görüntüleyebilirsiniz.

Ana uygulama:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Download files using a XHR2, a Worker, and saving to filesystem</title>
</head>
<body>
<script>
    var worker = new Worker('downloader.js');
    worker.onmessage = function(e) {
    console.log(e.data);
    };
    worker.postMessage({fileName: 'GoogleLogo',
                        url: 'googlelogo.png', type: 'image/png'});
</script>
</body>
</html>

downloader.js:

self.requestFileSystemSync = self.webkitRequestFileSystemSync ||
                                self.requestFileSystemSync;

function makeRequest(url) {
    try {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, false); // Note: synchronous
    xhr.responseType = 'arraybuffer';
    xhr.send();
    return xhr.response;
    } catch(e) {
    return "XHR Error " + e.toString();
    }
}

function onError(e) {
    postMessage('ERROR: ' + e.toString());
}

onmessage = function(e) {
    var data = e.data;

    // Make sure we have the right parameters.
    if (!data.fileName || !data.url || !data.type) {
    return;
    }
    
    try {
    var fs = requestFileSystemSync(TEMPORARY, 1024 * 1024 /*1MB*/);

    postMessage('Got file system.');

    var fileEntry = fs.root.getFile(data.fileName, {create: true});

    postMessage('Got file entry.');

    var arrayBuffer = makeRequest(data.url);
    var blob = new Blob([new Uint8Array(arrayBuffer)], {type: data.type});

    try {
        postMessage('Begin writing');
        fileEntry.createWriter().write(blob);
        postMessage('Writing complete');
        postMessage(fileEntry.toURL());
    } catch (e) {
        onError(e);
    }

    } catch (e) {
    onError(e);
    }
};

Sonuç

Web İşçileri, HTML5'in az kullanılan ve yeterince değer verilmeyen bir özelliğidir. Konuştuğum çoğu geliştiricinin ekstra hesaplama avantajlarına ihtiyacı yok ama sadece hesaplamadan daha fazlası için kullanılabilirler. Şüpheniz varsa (benim gibi), umarım bu makale fikrinizi değiştirmenize yardımcı olmuştur. Disk işlemleri (Filesystem API çağrıları) veya bir Çalışana HTTP istekleri gibi şeylerin boşaltılması doğal bir yöntemdir ve kodunuzu bölümlere ayırmanıza yardımcı olur. Çalışanlar'ın içindeki HTML5 Dosya API'ları, web uygulamaları için pek çok kişinin keşfetmediği yepyeni bir mükemmellik sunuyor.