FileSystem API sinkron untuk pekerja

Pengantar

FileSystem API dan Pekerja Web HTML5 sangat andal sesuai dengan kemampuan mereka. FileSystem API akhirnya menghadirkan penyimpanan hierarkis dan I/O file ke aplikasi web dan Pekerja menghadirkan 'multi-threading' asinkron yang sesungguhnya ke JavaScript. Namun, saat menggunakan API ini secara bersamaan, Anda dapat membangun beberapa aplikasi yang benar-benar menarik.

Tutorial ini memberikan panduan dan contoh kode untuk memanfaatkan FileSystem HTML5 di dalam Pekerja Web. Anda dianggap telah memiliki pengetahuan yang cukup tentang kedua API ini. Jika Anda belum cukup siap untuk mendalami atau tertarik mempelajari API tersebut lebih lanjut, baca dua tutorial hebat yang membahas dasar-dasar: Menjelajahi API FileSystem dan Dasar-Dasar Pekerja Web.

API sinkron vs. Asinkron

Asynchronous JavaScript API mungkin sulit digunakan. Ukurannya besar. Mereka rumit. Tapi yang paling membuat frustrasi adalah bahwa alat-alat ini menawarkan banyak kesempatan untuk melakukan kesalahan. Hal terakhir yang ingin Anda tangani adalah menambahkan lapisan pada API asinkron yang kompleks (FileSystem) di dunia (Workers) yang sudah asinkron. Kabar baiknya adalah FileSystem API mendefinisikan versi sinkron untuk meringankan kesulitan dalam Web Worker.

Untuk sebagian besar, API sinkron sama persis dengan API asinkronnya. Metode, properti, fitur, dan fungsi akan familier. Penyimpangan utamanya adalah:

  • API sinkron hanya dapat digunakan dalam konteks Pekerja Web, sedangkan API asinkron dapat digunakan di dalam dan di luar Pekerja.
  • Callback keluar. Metode API sekarang menampilkan nilai.
  • Metode global pada objek jendela (requestFileSystem() dan resolveLocalFileSystemURL()) menjadi requestFileSystemSync() dan resolveLocalFileSystemSyncURL().

Selain pengecualian ini, API-nya tetap sama. Oke, siap dimulai!

Meminta sistem file

Aplikasi web mendapatkan akses ke sistem file sinkron dengan meminta objek LocalFileSystemSync dari dalam Pekerja Web. requestFileSystemSync() diekspos ke cakupan global Pekerja:

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

Perhatikan nilai yang ditampilkan baru karena sekarang kita menggunakan API sinkron serta tidak adanya callback berhasil dan error.

Seperti halnya FileSystem API biasa, metode diberi awalan untuk saat ini:

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

Menangani kuota

Saat ini, meminta kuota PERSISTENT dalam konteks Pekerja tidak dapat dilakukan. Sebaiknya selesaikan masalah kuota di luar Pekerja. Prosesnya akan terlihat seperti ini:

  1. worker.js: Menggabungkan kode API FileSystem dalam try/catch sehingga error QUOTA_EXCEED_ERR dapat tertangkap.
  2. worker.js: Jika Anda menangkap QUOTA_EXCEED_ERR, kirim postMessage('get me more quota') kembali ke aplikasi utama.
  3. aplikasi utama: Lakukan tarian window.webkitStorageInfo.requestQuota() saat #2 diterima.
  4. aplikasi utama: Setelah pengguna memberikan lebih banyak kuota, kirim postMessage('resume writes') kembali ke pekerja untuk memberi tahunya tentang ruang penyimpanan tambahan.

Itu solusi yang cukup rumit, tetapi seharusnya berhasil. Lihat meminta kuota untuk informasi selengkapnya tentang penggunaan penyimpanan PERSISTENT dengan FileSystem API.

Bekerja dengan file dan direktori

Versi sinkron getFile() dan getDirectory() masing-masing menampilkan FileEntrySync dan DirectoryEntrySync.

Misalnya, kode berikut membuat file kosong bernama "log.txt" di direktori root.

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

Perintah berikut akan membuat direktori baru dalam folder root.

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

Menangani error

Jika Anda tidak perlu melakukan {i>debug<i} kode Web Worker, saya akan iri pada Anda! Mungkin menyakitkan untuk mencari tahu apa yang salah.

Tidak adanya callback error di dunia sinkron membuat penanganan masalah menjadi lebih rumit daripada yang seharusnya. Jika kami menambahkan kerumitan umum proses debug kode Web Worker, Anda akan segera frustrasi. Satu hal yang dapat membuat hidup lebih mudah adalah menggabungkan semua kode Pekerja yang relevan dalam try/catch. Kemudian, jika terjadi error, teruskan error tersebut ke aplikasi utama menggunakan postMessage():

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);
}

Meneruskan File, Blob, dan ArrayBuffer

Saat Pekerja Web pertama kali datang di adegan, mereka hanya mengizinkan data string dikirim di postMessage(). Kemudian, browser mulai menerima data yang dapat diserialisasi, yang berarti penerusan objek JSON dapat dilakukan. Namun, baru-baru ini beberapa browser seperti Chrome menerima jenis data yang lebih kompleks untuk diteruskan melalui postMessage() menggunakan algoritma clone terstruktur.

Apa artinya? Hal ini berarti akan jauh lebih mudah meneruskan data biner antara aplikasi utama dan thread Pekerja. Browser yang mendukung cloning terstruktur untuk Pekerja memungkinkan Anda meneruskan Array Berjenis, ArrayBuffer, File, atau Blob ke Pekerja. Meskipun data masih berupa salinan, kemampuan meneruskan File berarti manfaat performa dibandingkan pendekatan sebelumnya, yang melibatkan base64ing file sebelum meneruskannya ke postMessage().

Contoh berikut meneruskan daftar file yang dipilih pengguna ke Pekerja khusus. Pekerja cukup meneruskan daftar file (mudah untuk menampilkan bahwa data yang ditampilkan sebenarnya adalah FileList) dan aplikasi utama membaca setiap file sebagai ArrayBuffer.

Contoh ini juga menggunakan versi teknik Web Worker inline yang dijelaskan dalam Dasar-Dasar Pekerja Web.

<!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>

Membaca file dalam Worker

Anda dapat menggunakan FileReader API asinkron untuk membaca file di Worker. Namun, ada cara yang lebih baik. Di Pekerja, ada API sinkron (FileReaderSync) yang menyederhanakan pembacaan file:

Aplikasi utama:

<!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);

Seperti yang diharapkan, callback akan hilang dengan FileReader sinkron. Hal ini menyederhanakan jumlah penyusunan bertingkat callback saat membaca file. Sebagai gantinya, metode readAs* akan menampilkan file baca.

Contoh: Mengambil semua entri

Dalam beberapa kasus, API sinkron jauh lebih rapi untuk tugas tertentu. Lebih sedikit callback akan bagus dan tentu saja membuat semuanya lebih mudah dibaca. Kekurangan sebenarnya dari API sinkron berasal dari batasan Pekerja.

Untuk alasan keamanan, data antara aplikasi panggilan dan thread Web Worker tidak dibagikan. Data selalu disalin ke dan dari Pekerja saat postMessage() dipanggil. Akibatnya, tidak semua jenis data dapat diteruskan.

Sayangnya, FileEntrySync dan DirectoryEntrySync saat ini tidak termasuk dalam jenis yang diterima. Jadi bagaimana Anda bisa mendapatkan entri kembali ke aplikasi pemanggil? Salah satu cara untuk menghindari pembatasan ini adalah dengan menampilkan daftar filesystem: URLs, bukan daftar entri. URL filesystem: hanyalah string, sehingga sangat mudah untuk diteruskan. Selain itu, entri tersebut dapat diselesaikan di aplikasi utama menggunakan resolveLocalFileSystemURL(). Tindakan ini akan membawa Anda kembali ke objek FileEntrySync/DirectoryEntrySync.

Aplikasi utama:

<!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);
    }
};

Contoh: Mendownload file menggunakan XHR2

Kasus penggunaan umum bagi Pekerja adalah mendownload banyak file menggunakan XHR2, dan menulis file tersebut ke HTML5 FileSystem. Ini adalah tugas yang sempurna untuk thread Pekerja.

Contoh berikut hanya mengambil dan menulis satu file, tetapi Anda dapat memperluas gambar untuk mendownload kumpulan file.

Aplikasi utama:

<!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);
    }
};

Kesimpulan

Web Worker adalah fitur HTML5 yang kurang dimanfaatkan dan kurang diapresiasi. Developer yang saya ajak bicara tidak membutuhkan manfaat komputasi tambahan, tetapi mereka dapat digunakan untuk lebih dari sekadar komputasi. Jika Anda skeptis (seperti saya), kami harap artikel ini dapat membantu Anda berubah pikiran. Pemindahan muatan hal-hal seperti operasi disk (panggilan Filesystem API) atau permintaan HTTP ke Pekerja adalah hal yang wajar dan juga membantu memisahkan kode Anda. API File HTML5 di dalam Pekerja membuka kehebatan baru untuk aplikasi web yang belum dijelajahi oleh banyak orang.