Search code examples
javascriptmodal-dialogpromiseaureliabluebird

Bluebird promise pattern when creator cannot resolve


Bluebird deprecated their Promise.defer() mechanism for various reasons. While I think I understand the dangers of the defer-anti-pattern, I'm wondering if my use case requires it.

I want to return a promise that is resolved/rejected based on user input from a modal*. The modal is non-blocking and does not return a promise itself. Instead, functions are called when the user clicks on their response. Therefore, the function that constructs the promise does not have enough information to resolve/reject in its scope.

Javascript

export class FooClass {

  canDeactivate() {
    this.modal.open();          

    this.deferred = Promise.defer();  
    return this.deferred.promise;

    // How can I use `new Promise(resolver_func)` without defer migration?
  }

  cancel() {
    this.deferred.reject();
  }

  discard() {
    this.deferred.resolve();
  }
}

HTML

<div id="modal" md-modal md-modal.ref="modal">
  <div class="modal-content">
    <h4>Modal Header</h4>
    <p>A bunch of text</p>
  </div>
  <div class="modal-footer">
    <a click.delegate="discard()" ...>Discard Changes</a>
    <a click.delegate="cancel()" ...>Stay on Page</a>
  </div>
</div>

*This is an Aurelia app and my specific use case is to return a promise during the canDeactivate route lifecycle. A Materialize modal appears when the user tries to navigate away from the page asking if they want to discard changes and continue to navigate away or stay on the page.


Solution

  • There really are only two options with the standard promise executor function.

    1. You put the logic for calling resolve/reject inside the promise executor function and thus you can just call resolve/reject directly from there.

    2. You expose a means of calling resolve/reject or causing something else to call it from outside the executor by capturing resolve/reject into variables somewhere that are accessible from outside the executor.

    If the way you would like to architect your code just doesn't align with option #1 and you don't want to change the way your code is structured, then option #2 is what you're left with and the classic way to do that is with a deferred object.

    On the other hand, there is nearly always a way to restructure your code such that the promise executor is a closure around the logic that will trigger resolve/reject and the problem can be solved without a deferred. Which is a better way to do really depends on your specific case. I have seen what I would call legitimate cases for deferreds, but it's more like the 0.1% case and most of the time, a little restructuring of the code makes it line up just fine with the executor function of a promise.

    To offer ideas on the specifics of your case, we would need to understand the calling code to know how the overall operation could be morphed to fit into the promise executor without using a deferred.

    For example, if the code that was showing the dialog was also code that added event listeners for the cancel and discard buttons (rather than embedding that code in your HTML), then you could probably put all that code together in a promise executor and solve your problem just fine without a deferred and, in fact, more code out of your HTML (the concept of unobtrusive Javascript) which is generally considered a good thing.