Do promises work in Lightning?

Promises execute in a microtask which, by definition, breaks out of the Lightning event loop and the current Lightning access context. If you're unfamiliar with microtasks please read https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/.

If you need to preserve access context or need to reenter the Lightning event loop use $A.getCallback(). Eg

getAPromise().then(
  $A.getCallback(function resolve(value) { /* resolve handler */ }),
  $A.getCallback(function reject(error) { /* reject handler */ })
)

You need to be within the Lightning event loop when you enqueue an action (and you need to be within an access context to create the action in the first place). Eg

getAPromise().then(
  $A.getCallback(function resolve(value) { 
    var action = cmp.get("c.method");
    action.setParams({...});
    action.setCallback(scopeVar, function(response) {
      /* check response.getState() and handle accordingly */
    });
    $A.enqueueAction(action);
  }),
  $A.getCallback(function reject(error) { /* reject handler */ })
)

You can learn more about $A.getCallback() at https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cb_mod_ext_js.htm.

Some best practices to follow:

  1. Always include a reject handler or catch in your promise chain. If you need to report an error use $A.reportError(). Throwing an error in a promise will not trigger window.onerror, which is where Lightning hooks up the global error handler. Watch the JS console for reports about uncaught errors in a promise to verify you've done it correctly.

  2. Storable actions (https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_storable_actions.htm) may have their callbacks invoked more than once. This models the inherent mutable nature of Salesforce metadata and data. It's more like streams / reactive programming than most realize. This doesn't align well with the promise flow of 1-time resolve/reject state transition and thus callbacks are used in action.setCallback(scope, callbackFunction).