Simplifying Async JS with the Power of yield

Developing the Firefox UI

What I'm going to talk about tonight

Firefox Reader Mode

Firefox Reader Mode

Callback functions

JavaScript is event-driven

The event loop

More details at blog.carbonfive.com

Callback function


request('http://www.google.com', function(error, res, body) {
  console.log(body);
});
 
console.log('Done!');
Code from blog.carbonfive.com

Callback hell


fs.readdir(source, function(err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function(filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function(err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function(width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})
Code from callbackhell.com

Problems with callbacks

Example: Reader Mode with callbacks


var url = "http://foo.com/someArticle.html";
getArticle(url, function(article) {
  // Fill in a pretty article view
});

getArticle


function getArticle(url, callback) {
  getArticleFromCache(url, (article) => {
    if (article) {
      callback(article);
      return;
    }
    downloadDocument(url, (doc) => {
      parseDocument(doc, (article) => {
        storeArticleInCache(url, article);
        callback(article);
      });
    });
  });
}
See original at hg.mozilla.org

function downloadDocument(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.onerror = (event) => reject(event.error);
  xhr.responseType = "document";
  xhr.onload = (event) => {
    if (xhr.status !== 200) {
      callback(null);
      return;
    }
    var doc = xhr.responseXML;
    callback(doc);
  }
  xhr.send();
}

function parseDocument(doc, callback) {
  var worker = new Worker("readerWorker.js");
  worker.onmessage = (event) => {
    var article = event.data;
    callback(article);
  };

  worker.onerror = (event) => callback(null);
  worker.postMessage({
    uri: { ... },
    doc: new XMLSerializer().serializeToString(doc)
  });
}

Real world problems

By the end of this talk, we're going to fix this!

ES6 Lesson Time: Promises

Promises

Reference at developer.mozilla.org

var p = new Promise(function(resolve, reject) {
  var success = doSomething();
  if (success) {
    resolve("Success!");
  } else {
    reject("Error!");
  }
});

p.then((value) => {
  console.log(value); // Success!
}, (reason) => {
  console.log(reason); // Error!
});

p.catch((reason) => {
  console.log(reason); // Error!
});

var p = new Promise(function(resolve, reject) {
  resolve(1);
});

p.then((val) =>  {
  console.log(val); // 1
  return val + 2;
}).then((val) => {
  console.log(val); // 3
});
Code from html5rocks.com

Example: Reader Mode with promises


function downloadDocument(url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onerror = (event) => reject(event.error);
    xhr.responseType = "document";
    xhr.onload = (event) => {
      if (xhr.status !== 200) {
        reject("XHR failed with status: " + xhr.status);
        return;
      }
      var doc = xhr.responseXML;
      resolve(doc);
    }
    xhr.send();
  });
}

function parseDocument(doc) {
  return new Promise((resolve, reject) => {
    var worker = new Worker("readerWorker.js");

    worker.onmessage = function (event) {
      var article = event.data;
      resolve(article);
    };

    worker.onerror = (event) => reject(event.message);

    worker.postMessage({
      uri: { ... },
      doc: new XMLSerializer().serializeToString(doc)
    });
  });
}

function getArticle(url) {
  return getArticleFromCache(url).then((article) => {
    if (article) {
      return article;
    }
    return downloadDocument(url)
      .then(parseDocument)
      .then((article) => {
        storeArticleInCache(url, article);
        return article;
      });
  });
}

ES6 Lesson Time: Generators

Generators

Reference at developer.mozilla.org

Iterators

Reference at developer.mozilla.org

function* foo() {
  var index = 0;
  while(true) {
    yield index++;
  }
}

var it = foo();
it.next(); // { value: 0, done: false }
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }

function* foo(x) {
  yield x + 1;

  var y = yield;
  return x + y;
}

var it = foo(5);
it.next(); // { value: 6, done: false }
it.next(); // { value: undefined, done: false }
it.next(2); // { value: 7, done: true }

task.js

task.js

Original library at taskjs.org

Task.spawn()


Task.spawn(function* () {
  var result = yield promiseSomeValue();

  for (var i = 0; i < 3; i++) {
    result += yield promiseAnotherValue();
  }

  return result;
}).then(function (result) {
  console.log(result);
}, function (exception) {
  console.error(exception);
});
Code from hg.mozilla.org

Task.async()


var greeter = {
  msg: "Hello, NAME!",
  greet: Task.async(function* (name) {
    return yield sendGreeting(this.msg.replace(/NAME/, name));
  })
};

greeter.greet("Margaret").then(...);
Code from hg.mozilla.org

Example: Reader mode with tasks


var getArticle = Task.async(function* (url) {
  var article = yield getArticleFromCache(url);
  if (article) {
    return article;
  }

  var doc = yield downloadDocument(url);
  var article = yield parseDocument(doc);

  storeArticleInCache(url, article);
  return article;
});
See original at hg.mozilla.org

var url = "http://foo.com/someArticle.html";
getArticle(url).then((article) => {
  // Fill in a pretty article view
}, (error) => {
  // Handle an error!
});

Unit testing with tasks


add_task(function* test() {
  var result = yield Promise.resolve(true);
  do_check_true(result);

  var secondary = yield someFunctionThatReturnsAPromise(result);
  do_check_eq(secondary, "expected value");
});
See original at hg.mozilla.org

add_task(function* test_parse_articles() {
  for (var testcase of TEST_PAGES) {
    var article = yield getArticle(testcase.url);
    checkArticle(article, testcase);
  }
});
See original at hg.mozilla.org

Benefits of tasks

Thanks!