Skip to content

Coding with Q promises

jcheng5 edited this page Apr 19, 2013 · 1 revision

Shiny Server makes extensive use of the Q promises library. This document is intended to set coding standards for using Q in the Shiny Server codebase.

Resources

Coding Standards

Any variables that store a promise must be suffixed with Promise, for example resultPromise.

All functions/methods that return a promise must be suffixed with _p, for example downloadFile_p.

Any promise-handling chain MUST meet ONE of these conditions:

  1. The containing function returns the result of the chain (and the containing function itself thus should be named *_p).
  2. The chain terminates in .done(). NB: If any exceptions propagate through the chain without being handled, then the server process will terminate!
  3. The chain terminates in .eat(). Note that any exceptions that propagate through the chain will be silently discarded.

Good examples:

function getData_p(path) {
  return readFile_p(path)
  .then(function(contents) {
    return parseData(contents);
  });
}
function readAndSave() {
  readFile_p(path)
  .then(function(contents) {
    saveData(contents);
  })
  .fail(function(err) {
    logger.error(err.message);
  })
  .done();
}
function catAndErase(datafile) {
  // Yes, even finally() must be followed with eat() or done()!
  executeCommand_p('cat', datafile)
  .finally(function() {
    fs.unlinkSync(datafile);
  })
  .eat();
}

Bad examples:

// Calls .done() without handling errors--will blow up the process!

function readAndSave() {
  readFile_p(path)
  .then(function(contents) {
    saveData(contents);
  })
  .done();
}
// Bad function name--no suffix!

function getData(path) {
  return readFile_p(path)
  .then(function(contents) {
    return parseData(contents);
  });
}
// These are two separate chains, even though they refer to
// the same promise. The first one does not end with done()
// or eat(). ALL chains must end with done() or eat()!

function catAndErase(datafile) {
  var catPromise = executeCommand_p('cat', datafile);
  catPromise.finally(function() {
    fs.unlinkSync(datafile);
  });
  catPromise.eat();
}

This last example requires some explanation. Somewhere along the way, Q started tracking rejections that were not actually handled, so that they could emit warnings at process exit. This means that each exception that goes through this code path will be leaked forever. Over time this means we have to restart the server. So be diligent about following the rules for EVERY chain!