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

एरिक बिडेलमैन

शुरुआती जानकारी

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

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

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

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

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

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

इन अपवादों के अलावा, एपीआई एक जैसे होते हैं. ठीक है, हम स्विच करने के लिए तैयार हैं!

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

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

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

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

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

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

कोटा की समस्या

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

  1. वर्कर.js: किसी भी FileSystem API कोड को try/catch में रैप करें, ताकि QUOTA_EXCEED_ERR गड़बड़ियों का पता लगाया जा सके.
  2. वर्कर.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);
}

फ़ाइल, ब्लॉब, और अरेबफ़र के आस-पास से गुज़रना

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

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

नीचे दिया गया उदाहरण, उपयोगकर्ता की चुनी हुई फ़ाइलों की सूची एक खास वर्कर को पास करता है. वर्कर बस फ़ाइल की सूची से गुज़रता है (लौटाए गए डेटा को दिखाने में आसान यह एक 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 एपीआई का इस्तेमाल करना पूरी तरह से स्वीकार है. हालांकि, इसका एक बेहतर तरीका है. वर्कर्स में, एक सिंक्रोनस एपीआई (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: URLs की सूची दिखाएं. 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 FileSystem में लिखना है. वर्कर थ्रेड के लिए यह सबसे सही टास्क है!

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

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

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