Beautiful Off Main Thread File I/O

October 18, 2012 § 7 Comments

Now that the main work on Off Main Thread File I/O for Firefox is complete, I have finally found some time to test-drive the combination of Task.js and OS.File. Let me tell you one thing: it rocks!

Rather than a long discussion, let me show you a nice example Off Main Thread File I/O, written with the partial implementation of Task.js already present in the source code of the Mozilla Platform. The following snippet implements copying a file to another file by chunks:

let copy = function copy(sourcePath, destPath, chunkSize) {
  let sourceFile, destFile;
  return Task.spawn(function() {
    try {
      // Open input, output files
      sourceFile = yield OS.File.open(sourcePath);
      destFile = yield OS.File.open(destPath, {write: true, trunc: true});

      // Start copying
      let buffer = new Uint8Array(chunkSize);
      while(true) {
        let bytes = yield sourceFile.readTo(buffer);
        if (bytes == 0) return;
        let view;
        if (bytes < buffer.byteLength) {
          view = buffer.subarray(0, bytes);
        } else {
          view = buffer;
        }
        yield destFile.write(view);
     }
  } catch(x) {
    console.log("Copy failed", x);
  } finally {
    // Don't forget to close the files (double-closing is fine)
    if (destFile) {
      destFile.close();
    }
    if (sourceFile) {
      sourceFile.close();
    }
  });
};

Each occurrence of yield accept as right-hand side an expression that returns a promise, and produces as left-hand side the result with which the promise is resolved. Nice and readable.

For comparison, the following implements the same function without Task.js:

let copy2 = function copy2(sourcePath, destPath, chunkSize) {
  // Open source file;
  let sourceFile;

  let promise = OS.File.open(sourcePath);
  promise = promise.then(
    function onSuccess(file) {
      sourceFile = file;
    }
  );

  // Open destination file
  let destFile;
  promise = promise.then(
    function() {
      return OS.File.open(destPath, {write: true, trunc: true});
    }
  );
  promise = promise.then(
    function onSuccess(file) {
      destFile = file;
    }
  );

  // Prepare loop
  let buffer = new Uint8Array(chunkSize);
  let loop = function loop() {

    // Read chunk
    let promise = sourceFile.readTo(buffer);
    promise = promise.then(
      function onSuccess(bytes) {
        if (bytes == 0) {
          // Copy is complete
          return Promise.resolve(true);
        }
        let view;

        // Write chunk
        if (bytes < buffer.byteLength) {
          view = buffer.subarray(0, bytes);
        } else {
          view = buffer;
        }
        return destFile.write(view);
      }
    );

    // And loop
    promise = promise.then(loop);
    return promise;
  };

  // Start loop
  promise = promise.then(
    function onSuccess() {
      return loop(0);
    }
  );

  // Close files
  promise = promise.then(
    function onSuccess() {
      return sourceFile.close();
    }
  );

  promise = promise.then(
    function onSuccess() {
      return destFile.close();
    }
  );
  // Also close files in case of error
  promise = promise.then(
    null,
    function onFailure(reason) {
      console.error("Copy failed", reason);
      if (sourceFile) {
        sourceFile.close();
      }
      if (destFile) {
        destFile.close();
      }
      throw reason;
    }
  );
 return promise;
};

While the use of promises make this code much more readable than the spaghetti callbacks generally involved in asynchronous algorithms, in this case, using Task.js divides the line count by 3 and lets us use our beloved while loop.

Bottom line: I am quite excited about Task.js.

Research bottom line: Also, I have the feeling that the idea behind Task.js could be transposed to other forms of monads that do not necessarily involve asynchronicity or concurrency. I am sure that some ingenious mind will find a way to weaponize JavaScript generators into something even better than Task.js

Edit I had forgotten to close the files, this is now fixed. I took the opportunity to add a demonstration of error-handling.

Edit 2 After David Herman’s remark, I updated error handling to make it even nicer.

Tagged: , , , , , , , , , , ,

§ 7 Responses to Beautiful Off Main Thread File I/O

Leave a reply to Dave Herman Cancel reply

What’s this?

You are currently reading Beautiful Off Main Thread File I/O at Il y a du thé renversé au bord de la table.

meta