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()
danresolveLocalFileSystemURL()
) menjadirequestFileSystemSync()
danresolveLocalFileSystemSyncURL()
.
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:
- worker.js: Menggabungkan kode API FileSystem dalam
try/catch
sehingga errorQUOTA_EXCEED_ERR
dapat tertangkap. - worker.js: Jika Anda menangkap
QUOTA_EXCEED_ERR
, kirimpostMessage('get me more quota')
kembali ke aplikasi utama. - aplikasi utama: Lakukan tarian
window.webkitStorageInfo.requestQuota()
saat #2 diterima. - 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.