Chaining Promises, the right way

Most modern web applications rely on some form of asynchronous operations, for things like fetching data from a web service, or pushing data to the cloud. Until not long ago (~2014/2015), it was common-practice for developers to rely on callback functions for resuming operations at the completion of a particular async call. Callbacks solved the problem, but they often resulted in deeply-nested difficult-to-read code, colloquially known as “pyramids of doom”.

The JavaScript Promises pattern was created to solve the async-chaining problem in a cleaner way. At their core, Promises provide a way for chaining together multiple actions, in an easy-to-read and predictable order. To the extent that Promises are chained, the term “Breaking the Promise chain” refers to accidentally letting some of operations run loose, so that instead of one chain of operations, you accidentally end up with multiple parallel execution. This is bad because:

  • More often than not, logic that was meant to run in a certain order will break if the operations start running out of order. This leads to a class of not-always-reproducible and hard-to-diagnose bugs, known as timing issues.
  • In the case of Office.js APIs and the Excel.run(batch) (and Word.run(batch), etc.) family of methods, a broken Promise chain can lead to objects being inadvertently cleaned up before you’re done using them… leading to even harder-to-diagnose timing issues.
  • A broken Promise chain can leads to silently-swallowed errors, with the .catch statement handling only one of the thread chains, but not the other(s).

The good news is that in the new Office.js, the only asynchronous operation is “context.sync(). So – other than places in your code where you explicitly call a web service or do some other async operations – the only place where you need to watch out for with Office.js is just the context.sync() method. And moreover, there is exactly one simple rule you need to follow:

The part about returning a Promise is critical. Just calling context.sync() inside of the body of a function, without returning it, will lead to anything from errors being silently swallowed, to everything going completely awry.

For context.sync() in particular, there is a method for sidestepping the return issue altogether. You will read more about it in “Syncing object state”.