वर्कर के लिए सिंक्रोनस FileSystem API

परिचय

एचटीएमएल5 FileSystem API और वेब वर्कर्स, दोनों ही अपनी-अपनी जगह पर काफ़ी बेहतर हैं. FileSystem API की मदद से, वेब ऐप्लिकेशन में हैरारकी वाला स्टोरेज और फ़ाइल I/O की सुविधा मिलती है. साथ ही, वर्कर्स की मदद से JavaScript में असाइनोक्रोनस 'मल्टी-थ्रेडिंग' की सुविधा मिलती है. हालांकि, इन एपीआई का एक साथ इस्तेमाल करके, कुछ दिलचस्प ऐप्लिकेशन बनाए जा सकते हैं.

इस ट्यूटोरियल में, वेब वर्कर्स में HTML5 FileSystem का फ़ायदा पाने के लिए, गाइड और कोड के उदाहरण दिए गए हैं. यह मान लिया जाता है कि आपके पास दोनों एपीआई के बारे में जानकारी है. अगर आप अभी इन API का इस्तेमाल नहीं करना चाहते या आपको इनके बारे में ज़्यादा जानना है, तो इन दो ट्यूटोरियल पढ़ें. इनमें इन API के बारे में बुनियादी जानकारी दी गई है: FileSystem API के बारे में जानकारी और वेब वर्कर्स के बारे में बुनियादी जानकारी.

सिंक्रोनस बनाम एसिंक्रोनस एपीआई

असाइनोक्रोनस JavaScript एपीआई का इस्तेमाल करना मुश्किल हो सकता है. वे बड़े हों. वे जटिल हैं. हालांकि, सबसे परेशान करने वाली बात यह है कि इनमें गड़बड़ियां होने के कई मौके होते हैं. आपको ऐसे जटिल एसिंक्रोनस एपीआई (फ़ाइल सिस्टम) का इस्तेमाल नहीं करना चाहिए जो पहले से ही एसिंक्रोनस वर्ल्ड (वर्कर्स) में काम करता है! अच्छी बात यह है कि FileSystem API, वेब वर्कर्स में आने वाली समस्याओं को कम करने के लिए, सिंक्रोनस वर्शन तय करता है.

ज़्यादातर मामलों में, सिंक्रोनस एपीआई और असिंक्रोनस एपीआई एक जैसे ही होते हैं. इसके मेथड, प्रॉपर्टी, सुविधाएं, और फ़ंक्शन पहले से ही इस्तेमाल किए जा रहे हैं. मुख्य अंतर ये हैं:

  • सिंक्रोनस एपीआई का इस्तेमाल सिर्फ़ वेब वर्कर्स के संदर्भ में किया जा सकता है. वहीं, असिंक्रोनस एपीआई का इस्तेमाल, वर्कर्स के अंदर और बाहर किया जा सकता है.
  • कॉलबैक की सुविधा बंद है. एपीआई के तरीके अब वैल्यू दिखाते हैं.
  • विंडो ऑब्जेक्ट (requestFileSystem() और resolveLocalFileSystemURL()) पर मौजूद ग्लोबल तरीके, requestFileSystemSync() और resolveLocalFileSystemSyncURL() हो जाते हैं.

इन अपवादों के अलावा, एपीआई एक जैसे हैं. ठीक है, अब हम आगे बढ़ सकते हैं!

फ़ाइल सिस्टम का अनुरोध करना

वेब ऐप्लिकेशन, वेब वर्कर्स में LocalFileSystemSync ऑब्जेक्ट का अनुरोध करके, सिंक्रोनस फ़ाइल सिस्टम का ऐक्सेस पाता है. requestFileSystemSync() वर्कर के ग्लोबल स्कोप में दिखता है:

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

अब हम सिंक्रोनस एपीआई का इस्तेमाल कर रहे हैं. साथ ही, इसमें सफलता और गड़बड़ी के कॉलबैक मौजूद नहीं हैं. इसलिए, रिटर्न की नई वैल्यू पर ध्यान दें.

सामान्य FileSystem API की तरह ही, फ़िलहाल तरीकों के आगे प्रीफ़िक्स जोड़े जाते हैं:

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

कोटा से जुड़ी समस्या हल करना

फ़िलहाल, Worker के संदर्भ में PERSISTENT कोटा का अनुरोध नहीं किया जा सकता. हमारा सुझाव है कि आप Workers के अलावा, कोटा से जुड़ी अन्य समस्याओं को हल करें. यह प्रोसेस कुछ इस तरह दिख सकती है:

  1. worker.js: किसी भी FileSystem API कोड को try/catch में रैप करें, ताकि कोई भी QUOTA_EXCEED_ERR गड़बड़ी पकड़ी जा सके.
  2. worker.js: अगर आपको QUOTA_EXCEED_ERR मिलता है, तो मुख्य ऐप्लिकेशन को postMessage('get me more quota') भेजें.
  3. मुख्य ऐप्लिकेशन: #2 मिलने पर, window.webkitStorageInfo.requestQuota() डांस करें.
  4. मुख्य ऐप्लिकेशन: जब उपयोगकर्ता ज़्यादा कोटा दे देता है, तो अतिरिक्त स्टोरेज के बारे में बताने के लिए, postMessage('resume writes') को वर्कफ़्लो में वापस भेजें.

यह समस्या हल करने का एक मुश्किल तरीका है, लेकिन यह काम करेगा. FileSystem API के साथ PERSISTENT स्टोरेज का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, कोटा का अनुरोध करना देखें.

फ़ाइलों और डायरेक्ट्री के साथ काम करना

getFile() और getDirectory() के सिंक किए गए वर्शन, क्रमशः FileEntrySync और DirectoryEntrySync दिखाते हैं.

उदाहरण के लिए, यह कोड रूट डायरेक्ट्री में "log.txt" नाम की खाली फ़ाइल बनाता है.

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

इससे रूट फ़ोल्डर में एक नई डायरेक्ट्री बनती है.

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

गड़बड़ियों को मैनेज करना

अगर आपको कभी भी वेब वर्कर्स कोड को डीबग नहीं करना पड़ा है, तो मुझे आपसे जलन हो रही है! यह पता लगाना मुश्किल हो सकता है कि आखिर क्या गड़बड़ी है.

सिंक्रोनस वर्ल्ड में गड़बड़ी के कॉलबैक की कमी की वजह से, समस्याओं को हल करना ज़्यादा मुश्किल हो जाता है. अगर हम वेब वर्कर्स कोड को डीबग करने की सामान्य जटिलता को जोड़ते हैं, तो आपको तुरंत परेशानी होगी. अपने सभी काम के Worker कोड को try/catch में रैप करना, काम को आसान बना सकता है. इसके बाद, अगर कोई गड़बड़ी होती है, तो 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);
}

फ़ाइलें, ब्लॉब, और ArrayBuffers पास करना

वेब वर्कर्स की सुविधा पहली बार लॉन्च होने पर, postMessage() में सिर्फ़ स्ट्रिंग डेटा भेजने की अनुमति थी. बाद में, ब्राउज़र ने सीरियलाइज़ किए जा सकने वाले डेटा को स्वीकार करना शुरू कर दिया. इसका मतलब है कि JSON ऑब्जेक्ट को पास किया जा सकता था. हालांकि, हाल ही में Chrome जैसे कुछ ब्राउज़र, postMessage() के ज़रिए स्ट्रक्चर्ड क्लोन एल्गोरिदम का इस्तेमाल करके, ज़्यादा जटिल डेटा टाइप स्वीकार करते हैं.

इसका क्या मतलब है? इसका मतलब है कि मुख्य ऐप्लिकेशन और वर्क थ्रेड के बीच, बाइनरी डेटा को पास करना काफ़ी आसान है. ऐसे ब्राउज़र जिनमें वर्कर्स के लिए स्ट्रक्चर्ड क्लोनिंग की सुविधा काम करती है, उनमें टाइप किए गए कलेक्शन, ArrayBuffer, File या Blob को वर्कर्स में पास किया जा सकता है. हालांकि, डेटा अब भी एक कॉपी है, लेकिन File को पास करने का मतलब है कि पहले के तरीके की तुलना में परफ़ॉर्मेंस में फ़ायदा होगा. पहले के तरीके में, फ़ाइल को postMessage() में पास करने से पहले, उसे base64 में बदलना पड़ता था.

इस उदाहरण में, उपयोगकर्ता की चुनी गई फ़ाइलों की सूची को किसी खास Worker को भेजा गया है. Worker, फ़ाइल की सूची को आसानी से पास करता है. यह दिखाने के लिए कि लौटाए गए डेटा में असल में FileList है, यह आसान है. मुख्य ऐप्लिकेशन, हर फ़ाइल को ArrayBuffer के तौर पर पढ़ता है.

सैंपल में, वेब वर्कर्स के बुनियादी सिद्धांतों में बताई गई इनलाइन वेब वर्कर्स तकनीक के बेहतर वर्शन का भी इस्तेमाल किया गया है.

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

Worker में फ़ाइलें पढ़ना

वर्कर्स में फ़ाइलें पढ़ने के लिए, एसिंक्रोनस FileReader एपीआई का इस्तेमाल करना पूरी तरह से सही है. हालांकि, इसके लिए एक बेहतर तरीका है. Workers में, एक सिंक्रोनस एपीआई (FileReaderSync) होता है, जो फ़ाइलों को पढ़ने की प्रोसेस को आसान बनाता है:

मुख्य ऐप्लिकेशन:

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

उम्मीद के मुताबिक, सिंक्रोनस FileReader के साथ कॉलबैक की सुविधा बंद कर दी गई है. इससे फ़ाइलें पढ़ते समय, कॉलबैक नेस्टिंग की संख्या को आसानी से कम किया जा सकता है. इसके बजाय, readAs* तरीके से पढ़ी गई फ़ाइल मिलती है.

उदाहरण: सभी एंट्री फ़ेच करना

कुछ मामलों में, सिंक्रोनस एपीआई कुछ टास्क के लिए ज़्यादा बेहतर होता है. कम कॉलबैक का मतलब है कि कोड को पढ़ना आसान हो जाता है. सिंक्रोनस एपीआई की असली समस्या, वर्कर्स की सीमाओं से जुड़ी है.

सुरक्षा के लिहाज़ से, कॉल करने वाले ऐप्लिकेशन और वेब वर्कर्स थ्रेड के बीच डेटा कभी शेयर नहीं किया जाता. postMessage() को कॉल करने पर, डेटा को हमेशा Worker में और उससे कॉपी किया जाता है. इस वजह से, हर डेटा टाइप को पास नहीं किया जा सकता.

माफ़ करें, फ़िलहाल FileEntrySync और DirectoryEntrySync, स्वीकार किए गए टाइप में शामिल नहीं हैं. तो कॉल करने वाले ऐप्लिकेशन में, कॉल की जानकारी वापस कैसे देखी जा सकती है? इस पाबंदी को बायपास करने का एक तरीका यह है कि एंट्री की सूची के बजाय, फ़ाइल सिस्टम: यूआरएल की सूची दिखाएं. filesystem: यूआरएल सिर्फ़ स्ट्रिंग होते हैं, इसलिए उन्हें शेयर करना बहुत आसान होता है. इसके अलावा, resolveLocalFileSystemURL() का इस्तेमाल करके, इन्हें मुख्य ऐप्लिकेशन में मौजूद एंट्री में बदला जा सकता है. इससे आपको FileEntrySync/DirectoryEntrySync ऑब्जेक्ट पर वापस ले जाया जाता है.

मुख्य ऐप्लिकेशन:

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

उदाहरण: XHR2 का इस्तेमाल करके फ़ाइलें डाउनलोड करना

वर्कर्स का इस्तेमाल आम तौर पर, XHR2 का इस्तेमाल करके कई फ़ाइलें डाउनलोड करने और उन फ़ाइलों को HTML5 फ़ाइल सिस्टम में लिखने के लिए किया जाता है. यह Worker थ्रेड के लिए एक बेहतरीन टास्क है!

यहां दिए गए उदाहरण में सिर्फ़ एक फ़ाइल फ़ेच और लिखी गई है. हालांकि, फ़ाइलों का एक सेट डाउनलोड करने के लिए, इसे बड़ा किया जा सकता है.

मुख्य ऐप्लिकेशन:

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

नतीजा

वेब वर्कर, HTML5 की एक ऐसी सुविधा है जिसका इस्तेमाल बहुत कम किया जाता है और जिसे ज़्यादा अहमियत नहीं दी जाती. जिन डेवलपर से मैंने बात की है उनमें से ज़्यादातर को कंप्यूटेशन से जुड़े अतिरिक्त फ़ायदों की ज़रूरत नहीं है. हालांकि, इनका इस्तेमाल सिर्फ़ कंप्यूटेशन के लिए ही नहीं किया जा सकता. अगर आपको भी इस बारे में संदेह है (जैसा कि मुझे था), तो उम्मीद है कि इस लेख से आपको अपने फ़ैसले में बदलाव करने में मदद मिली होगी. डिस्क ऑपरेशन (फ़ाइल सिस्टम एपीआई कॉल) या एचटीटीपी अनुरोधों जैसी चीज़ों को वर्कर्स पर ऑफ़लोड करना, एक स्वाभाविक फ़ैसला है. इससे आपके कोड को अलग-अलग हिस्सों में बांटने में भी मदद मिलती है. वर्कर्स के अंदर मौजूद एचटीएमएल5 फ़ाइल एपीआई, वेब ऐप्लिकेशन के लिए एक नई दुनिया खोलते हैं. इस दुनिया को अब तक बहुत कम लोगों ने एक्सप्लोर किया है.