Behavior of Asynchronous Calls

By default, calls are asynchronous since many of them may require a roundtrip to the server. Methods use the following naming convention:

  • Asynchronous calls are indicated by an Async suffix on the method name, for example, Worksheet.applyFilterAsync().

  • Asynchronous calls return a Promise object, allowing chaining.

The Tableau JavaScript API uses the CommonJS Promises/A standard(Link opens in a new window). The premise behind Tableau's implementation is that asynchronous methods return an object that has a then method in the following form:

.then(fulfilledHandler, errorHandler)

The fulfilledHandler is called when the promise is fulfilled (on success). The errorHandler is called when a promise fails. All arguments are optional and non-function values are ignored.

Chaining Promises

The promised result of an asynchronous method is passed as the parameter to the next then() method. For example:

var activeSheet;

viz.getWorkbook().activateSheetAsync("Sheet 1")
  .then(selectLemon).then(filterToLemonAndMint);

function selectLemon(sheet) {
  activeSheet = sheet;
  return sheet.selectMarksAsync("Product", "Lemon", "replace");
}

function filterToLemonAndMint() {
  return activeSheet.applyFilterAsync("Product", ["Lemon", "Mint"], "replace");
}

The result of activateSheetAsync() is a promise to eventually return the Sheet object that was activated, which is passed as the first parameter to the selectLemon() method. Notice that the selectLemon() method returns a Promise object (the return value of the selectMarksAsync() method), not the result after the marks have been selected. However, since it's a Promise object, the next then() method is not called until that promise is fulfilled.

If a link in the chain is added after the promise has been fulfilled, the callback will be immediately called with the value that was previously returned. As the programmer, this means you don't need to determine if the response has already been received from the server. The asynchronous methods will always be called, whether it's now or later.

var promise = viz.getWorkbook().activateSheetAsync("Sheet 1");
// Pretend that activatSheeteAsync() has already returned from the server.
promise.then(callback);
// callback will be called immediately using the Sheet object
// returned from activateSheetAsync()

Return Values of Then() Methods

Whatever is returned in a then() method will get passed as the first parameter to the next then() method. It can be a scalar value (Number, Boolean, String, etc.), an object, or another Promise. The infrastructure will automatically wrap non-Promise values into a Promise value so that they can be chained.

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then(function (sheet) {
     return "First link";
})
.then(function (message) {
     if (message === "First link") { alert("Expected"); }
     // no return value here means nothing is passed to the next link
})
.then(function () {
});

Breaking Out of a Chain

Technically, there's no way to break out of a chain since that would invalidate the guarantee that subsequent links in the chain will be called. If there is an exception thrown in part of the chain, the rest of the chain is run but the errorHandler is called instead of the fulfilledHandler.

If a link in the chain depends on the results of earlier links, then you should write an if statement to check your condition. Here's an example:

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then(function (sheet) {
  // I'm returning a Promise
  return sheet.selectMarksAsync("Product", "NoProduct", "replace");
})
.then(function () {
  return viz.getWorkbook().getActiveSheet().getSelectedMarksAsync();
})
.then(function (marks) {
  // The getSelectedMarksAsync call succeeded, but no marks were selected
  // because there are not any marks corresponding to "NoProduct".
  if (marks.length === 0) {
    throw new Error("No marks selected");
  }
  var firstMarkValue = marks[0].getPairs().get("Product").value;
  return sheet.applyFilterAsync("Product", firstMarkValue, "replace");
})
.then(function (filterName) {
  // applyFilterAsync succeeded
}, function(err) {
  if (err.message === "No marks selected") {
    alert("This was caused by the first link above");
  }
})
.otherwise(function (err) {
  alert("We handled the error above, so it's not propagated to this handler.");
});

If a callback is not provided (or is null or undefined), then the results are passed to the next link in the chain:

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then()
.then(function (sheet) {
  // this is called
});

In this way, you can specify a single otherwise function to handle all errors in the chain. The always function works the same way, but it is called regardless of success or failure. The then/otherwise/always functions work similarly to a try/catch/finally block.

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then(function () {
     return sheet.selectMarksAsync(...);
})
.then(function (marks) {
     // Do something with the marks.
})
.otherwise(function (err) {
     // I'm handling all errors in one place.
     console.log(err.message);
})
.always(function () {
    // Do some cleanup or logging
});

Thanks for your feedback!Your feedback has been successfully submitted. Thank you!