This article is partially or completely unfinished. You are welcome to create pull requests to help completing this article.

Typically promises are used in conjunction with asynchronous tasks such as a network request or a setTimeout; a lesser explored use is dealing with user input. Since a program has to wait for a user to continue some actions it makes sense to consider it an asynchronous event.

For comparison I'll start with an example of a synchronous user interaction using window.prompt and then move to an asynchronous interaction by making our own DOM based prompt. To begin, here is a template for a simple HTML page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Async Dislogs Example</title>
  <script src="//cdn.jsdelivr.net/bluebird/3.7.2/bluebird.js"></script>
  <script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function() {
      var time = document.getElementById('time-stamp');
      clockTick();
      setInterval(clockTick, 1000);
      function clockTick() {
        time.innerHTML = new Date().toLocaleTimeString();
      }
    });
  </script>
</head>
<body>
  <p>The current time is <span id="time-stamp"></span>.</p>
  <p>Your name is <span id="prompt"></span>.</p>
  <button id="action">Set Name</button>
</body>
</html>

window.prompt blocks the web page from processing while it waits for the user to enter in data. It has to block because the input is returned and the next line of code needs that result. But for sake of this tutorial we are going to convert the typical conditional code into a promise API using a promise constructor.

function promptPromise(message) {
  return new Promise(function(resolve, reject) {
    var result = window.prompt(message);
    if (result != null) {
      resolve(result);
    } else {
      reject(new Error('User cancelled'));
    }
  });
}

var button = document.getElementById('action');
var output = document.getElementById('prompt');

button.addEventListener('click', function() {
  promptPromise('What is your name?')
    .then(function(name) {
      output.innerHTML = String(name);
    })
    .catch(function() {
      output.innerHTML = '¯\\_(ツ)_/¯';
    });
});

Run example on JSBin

This doesn't add much much using window.prompt; however, one advantage is the API that promises provide. In the case where we call promptPromise(…) we can easily react to the result of the dialog without having to worry about how it is implemented. In our example we've implemented the window.prompt but our call to promptPromise() doesn't care. This makes a change to an asynchronous dialog a little more future proof.

To drive home the synchronous nature of the window.prompt notice that the time stops ticking when the prompt dialog is displayed. Let's fix that by making our own prompt. Since our dialog is just DOM manipulation the page won't be blocked while waiting for user input.

First add the prompt dialog to the HTML:

<style type="text/css">
  #dialog {
    width:      200px;
    margin:     auto;
    padding:    10px;
    border:     thin solid black;
    background: lightgreen;
  }
  .hidden {
    display: none;
  }
</style>
<div id="dialog" class="hidden">
  <div class="message">foobar</div>
  <input type="text">
  <div>
    <button class="ok">Ok</button>
    <button class="cancel">Cancel</button>
  </div>
</div>

We will want to keep the same API so our change will be only to the promisePrompt. It will find the dialog DOM elements, attach events to the elements, show the dialog box, return a promise that is resolved based on the attached events, and finally detaches the events and cleans up after itself (hiding the dialog box for another use later).

function promptPromise(message) {
  var dialog       = document.getElementById('dialog');
  var input        = dialog.querySelector('input');
  var okButton     = dialog.querySelector('button.ok');
  var cancelButton = dialog.querySelector('button.cancel');

  dialog.querySelector('.message').innerHTML = String(message);
  dialog.className = '';

  return new Promise(function(resolve, reject) {
    dialog.addEventListener('click', function handleButtonClicks(e) {
      if (e.target.tagName !== 'BUTTON') { return; }
      dialog.removeEventListener('click', handleButtonClicks);
      dialog.className = 'hidden';
      if (e.target === okButton) {
        resolve(input.value);
      } else {
        reject(new Error('User cancelled'));
      }
    });
  });
}

Run example on JSBin

Now when the user presses the Set Name button the clock continues to update while the dialog is visible.

Because the removeEventListener requires a reference to the original function that was used with the addEventListener it makes it difficult to clean up after itself without storing the references in a scope higher then the handler itself. Using a named function we can reference it when a user clicks the button. To help with performance and to avoid duplicating code the example uses event delegation to capture both buttons in one click handler.

The same thing can be done with less code using jQuery's event namespacing.

return new Promise(function(resolve, reject) {
  $('#okButton').on('click.promptDialog', function() {
    resolve(input.value);
  });
  $('#cancelButton').on('click.promptDialog', reject);
})
.finally(function() {
  $('#okButton').off('click.promptDialog');
  $('#cancelButton').off('click.promptDialog');
});

There are still a few problems with the earlier code example. It feels like it is doing too much. A squint test reveals behavior for showing the dialog, set the dialog's message, attach two DOM events, construct a promise, event delegation, hide the dialog, and finally detach DOM events. That is a lot for one little function. A refactoring can help.

Abstraction is the key here. We will make an object (or class) that is responsible for managing the dialog box. Its interface will manage only two function references (callbacks): when the user clicks ok and when user clicks cancel. And it will offer the value when asked.

Using an abstraction like this the promisePrompt no longer needs to know anything about the DOM and concentrates on just providing a promise. This will also make things easier to create a promised version of a progress bar or confirmation dialog or any other type of UI that we want to have a value for. All we will need to do is write a class for that dialog type with the same interface and just pass that class into our promise making method.

The dialog interface might look like this:

var noop = function() {
  return this;
};

function Dialog() {
  this.setCallbacks(noop, noop);
}
Dialog.prototype.setCallbacks = function(okCallback, cancelCallback) {
  this._okCallback     = okCallback;
  this._cancelCallback = cancelCallback;
  return this;
};
Dialog.prototype.waitForUser = function() {
  var _this = this;
  return new Promise(function(resolve, reject) {
    _this.setCallbacks(resolve, reject);
  });
};
Dialog.prototype.show = noop;
Dialog.prototype.hide = noop;

Initially the Dialog class sets the two callbacks to noop functions. It is up to the child class to call them when necessary. We break down the promise creation to one function waitForUser() that sets the callbacks and returns a promise. At this level the show() and hide() are just noop functions as well and will be implemented by the child classes.

Our PromptDialog class is responsible for inheriting from Dialog and setting up the required DOM scaffolding and eventually call this._okCallback or this._cancelCallback as appropriate.

It might look like this:

function PromptDialog() {
  Dialog.call(this);
  this.el           = document.getElementById('dialog');
  this.inputEl      = this.el.querySelector('input');
  this.messageEl    = this.el.querySelector('.message');
  this.okButton     = this.el.querySelector('button.ok');
  this.cancelButton = this.el.querySelector('button.cancel');
  this.attachDomEvents();
}
PromptDialog.prototype = Object.create(Dialog.prototype);
PromptDialog.prototype.attachDomEvents = function() {
  var _this = this;
  this.okButton.addEventListener('click', function() {
    _this._okCallback(_this.inputEl.value);
  });
  this.cancelButton.addEventListener('click', function() {
    _this._cancelCallback();
  });
};
PromptDialog.prototype.show = function(message) {
  this.messageEl.innerHTML = String(message);
  this.el.className = '';
  return this;
};
PromptDialog.prototype.hide = function() {
  this.el.className = 'hidden';
  return this;
};

Notice that use of return this; in most of the functions? That pattern will allow method chaining as you'll see shortly.

This inherits from Dialog and stores references to the required DOM elements that this dialog uses. It then attaches the require DOM events (attachDomEvents()) which eventually call the callbacks. Then it implements the show() and hide() methods. Its usage is more flexible and verbose:

var output = document.getElementById('prompt');
var prompt = new PromptDialog();

prompt.show('What is your name?')
  .waitForUser()
  .then(function(name) {
    output.innerHTML = String(name);
  })
  .catch(function() {
    output.innerHTML = '¯\\_(ツ)_/¯';
  })
  .finally(function() {
    prompt.hide();
  });

Run example on JSBin

This abstraction can be expanded on in other ways. For example a notification dialog:

function NotifyDialog() {
  Dialog.call(this);
  var _this      = this;
  this.el        = document.getElementById('notify-dialog');
  this.messageEl = this.el.querySelector('.message');
  this.okButton  = this.el.querySelector('button.ok');
  this.okButton.addEventListener('click', function() {
    _this._okCallback();
  });
}
NotifyDialog.prototype = Object.create(Dialog.prototype);
NotifyDialog.prototype.show = function(message) {
  this.messageEl.innerHTML = String(message);
  this.el.className = '';
  return this;
};
NotifyDialog.prototype.show = function() {
  this.el.className = 'hidden';
  return this;
};

Exercises for the student

  1. Write a function that takes a Dialog instance and a default value. Have it return a promise that resolves to the default value if the user clicks cancel.
  2. With the use of abstract classes can the similarities between PromptDialog and NotifyDialog be abstracted? Make a sub class of Dialog that abstracts the common DOM code (DOMDialog). Then refactor the PromptDialog and NotifyDialog to inherate from DOMDialog but references the correct DOM selectors.

Cancellation

Something missing from the above example is proper error handling. When it comes to promises it is a best practise to always reject a promise with an Error and not with plain data such as an object, string, number, or null/undefined. The reasoning for this is promises are best used as a way to regain some of the syntax you have with the standard try {} catch() {} blocks with asynchronous code.

An advantage of using Errors is the ability to test why a promise was rejected and make decisions on that. This ability is also baked into how Bluebird works. You can pass in a predicate to the catch() block allowing you to have more than one block based on what Error it was rejected with. For example:

doSomething().then(function(value) {
  // Do something with value or fail with an error.
  throw new Error('testing errors');
})
.catch(ArgumentError, function(e) {
  console.log('You buggered up something with the arguments.', e);
})
.catch(SyntaxError, function(e) {
  console.log('Check your syntax!', e);
})
.catch(function(e) {
  // e is an Error object.
  console.log('Well something genaric happened.', e);
});

In our dialog example perhaps we want to differentiate between a rejected promise because of some problem (bad AJAX, programming error, etc.) or because the user pressed the cancel button.

To do this we will have two catch() functions one for UserCanceledError and one for any other Error. We can make a custom error like so:

function UserCanceledError() {
  this.name = 'UserCanceledError';
  this.message = 'Dialog cancelled';
}
UserCanceledError.prototype = Object.create(Error.prototype);

See this StackOverflow answer for a more detailed and feature complete way to make custom errors.

Now we can add a cancel() reject with this in our event listener:

Dialog.prototype.cancel = function() {
  this._cancelCallback(new UserCanceledError());
};



PromptDialog.prototype.attachDomEvents = function() {
  var _this = this;
  this.okButton.addEventListener('click', function() {
    _this._okCallback(_this.inputEl.value);
  });
  this.cancelButton.addEventListener('click', function() {
    _this.cancel();
  });
};

And in our usage case we can test for it:

// Timeout the dialog in five seconds.
setTimeout(function() { prompt.cancel(); }, 5000);

prompt.show('What is your name?')
  .waitForUser()
  .then(function(name) {
    output.innerHTML = String(name);
  })
  .catch(UserCanceledError, function() {
    output.innerHTML = '¯\\_(ツ)_/¯';
  })
  .catch(function(e) {
    console.log('Something bad happened!', e);
  })
  .finally(function() {
    prompt.hide();
  });

Run example on JSBin

NOTE: Bluebird supports cancellation as an optional feature that is turned off by default. However, its implementation (since version 3.0) is meant to stop the then and catch callbacks from firing. It is not helpful in the example of a user cancellation as described here.

Progress bar

When there are asynchronous tasks that have the ability to notify progress as they complete it can be tempting to want that in the promise that represents that task. Unfortunately this is a bit of an anti-pattern. That is because the point of promises is to represent a value as if it was natural (like it is in normal synchronous code) and not to be over glorified callback management.

So how then could we represent a progress bar like dialog? Well the answer is to manage the progress through callbacks outside the promise API. Bluebird has since deprecated the progression feature and offers an alternative which I hope to illustrate here.

Another key difference between a progress bar dialog and any other dialog we've discussed here is that a progress bar represents information on another task and not user import. Instead of the program waiting for the user to provide a value the dialog box is waiting on the program to provide a value (resolved: 100% complete, rejected: aborted half way through). Because of this the progress bar dialog would have a different interface then the previous dialogs we've covered. However, there can still be some user interaction so in essence we are dealing with two promises.

Bluebird has a way to manage more than one promise simultaneously. When you want to know if more then one promise completes there is a Promise.all() function that takes an array of promises and returns a new promise waiting for them all to resolve. But if any one is rejected the returned promise is immediately rejected.

Bluebird also has a Promise.race() function which does the same thing but doesn't wait for all of them to finish. That is what we want. An example how this might look:

function showProgress(otherPromise) {
  var progress = new ProgressbarDialog().show('Uploading…');
  return Promise.race([otherPromise, promise.waitForUser()])
    .finally(function() {
      progress.hide();
    });
}

Here is some example HTML for the Progress Dialog:

<style type="text/css">
  #progress-dialog {
    width:      200px;
    margin:     auto;
    border:     thin solid black;
    padding:    10px;
    background: lightgreen;
  }
  #progress-dialog .progress-bar {
    border:  1px solid black;
    margin:  10px auto;
    padding: 0;
    height:  20px;
  }
  #progress-dialog .progress-bar>div {
    background-color: blue;
    margin:           0;
    padding:          0;
    border:           none;
    height:           20px;
  }
</style>
<div id="progress-dialog">
  <div class="message"></div>
  <div class="progress-bar"><div></div></div>
  <div>
    <button class="cancel">Cancel</button>
  </div>
</div>

The JavaScript is the same as the PromptDialog only we will add a setProgress() method:

function ProgressDialog() {
  Dialog.call(this);
  this.el           = document.getElementById('progress-dialog');
  this.messageEl    = this.el.querySelector('.message');
  this.progressBar  = this.el.querySelector('.progress-bar>div');
  this.cancelButton = this.el.querySelector('button.cancel');
  this.attachDomEvents();
}
ProgressDialog.prototype = Object.create(Dialog.prototype);
ProgressDialog.prototype.attachDomEvents = function() {
  var _this = this;
  this.cancelButton.addEventListener('click', function() {
    _this.cancel();
  });

};
ProgressDialog.prototype.show = function(message) {
  this.messageEl.innerHTML = String(message);
  this.el.className = '';
  return this;
};
ProgressDialog.prototype.hide = function() {
  this.el.className = 'hidden';
  return this;
};
ProgressDialog.prototype.setProgress = function(percent) {
  this.progressBar.style.width = percent + '%';
};

A common misconception is that promises are a form of callback management. This is not the case and is why the idea of having a progress callback is not part of the Promise spec. However, much like the Promise library passes in a resolve and reject callback when you create a new promise (new Promise(…)) we can do the same patter for a progress callback.

Now to the fun part. For this tutorial we will fake a lengthy file upload by using setTimeout. The intent is to provide a promise and to allow a progress to be periodically ticked away. We will expect a function to be passed which is called whenever the progress needs updating. And it returns a promise.

function delayedPromise(progressCallback) {
  var step = 10;
  return new Promise(function(resolve, reject) {
    var progress = 0 - step; // So first run of nextTick will set progress to 0
    function nextTick() {
      if (progress >= 100 ) {
        resolve('done');
      } else {
        progress += step;
        progressCallback(progress);
        setTimeout(nextTick, 500);
      }
    }
    nextTick();
  });
}

When we construct our ProgressDialog we use the waitForUser() method to capture the user interaction promise and then use delayedPromise() to capture the fake network promise and finally Promise.reace() to manage the two simultaneously and end with a single promise as usual.

document.addEventListener('DOMContentLoaded', function() {
  var button = document.getElementById('action');
  var output = document.getElementById('output');

  var prompt = new ProgressDialog();

  button.addEventListener('click', function() {
    var pendingProgress = true;
    var waitForPromise = delayedPromise(function(progress) {
      if (pendingProgress) {
        prompt.setProgress(progress);
      }
    });

    // Prevent user from pressing button while dialog is visible.
    button.disabled = true;

    prompt.show('Simulating a file upload.');

    Promise.race([waitForPromise, prompt.waitForUser()])
      .then(function() {
        output.innerHTML = 'Progress completed';
      })
      .catch(UserCanceledError, function() {
        output.innerHTML = 'Progress canceled by user';
      })
      .catch(function(e) {
        console.log('Error', e);
      })
      .finally(function() {
        pendingProgress = false;
        button.disabled = false;
        prompt.hide();
      });
  });
});

Run example on JSBin

I hope this helps illustrate some concepts available with Promises and a different perspective on how promises can represent more then just AJAX data.

Although the code may look verbose it does provide the benefit that it is modular and can be easily changed. A trait difficult to achieve with a more procedural style.

Happy coding, @sukima.