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

परिचय

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

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

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

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

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

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

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

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

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

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

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

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

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

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

फ़िलहाल, वर्कर के लिए 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});

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

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

सिंक्रोनस वर्ल्ड में गड़बड़ी के कॉलबैक की कमी की वजह से, समस्याओं को हल करना ज़्यादा मुश्किल हो जाता है. अगर हम वेब वर्कर्स कोड को डीबग करने की सामान्य जटिलता को जोड़ते हैं, तो आपको तुरंत परेशानी होगी. एक चीज़, जिससे काम आसान हो सकता है, वह है सभी काम के वर्कर कोड को एक कोशिश/कैच में रैप करना. इसके बाद, अगर कोई गड़बड़ी आती है, तो 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, फ़ाइल की सूची को आसानी से पास करता है. यह दिखाने के लिए कि लौटाए गए डेटा को 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>

किसी वर्कर में फ़ाइलें पढ़ना

वर्कर्स में फ़ाइलें पढ़ने के लिए, एसिंक्रोनस 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() को कॉल करने पर, डेटा हमेशा वर्कर को कॉपी किया जाता है या उससे कॉपी होता है. इस वजह से, हर डेटा टाइप को पास नहीं किया जा सकता.

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