function doInParallel(items, beginProcessItem, failFast) {
        var promises = [];
        var masterDeferred = new $.Deferred();

        if (items.length === 0) {
            masterDeferred.resolve();

        } else {
            var numCompleted = 0;
            var hasFailed = false;

            items.forEach(function (item, i) {
                var itemPromise = beginProcessItem(item, i);
                promises.push(itemPromise);

                itemPromise.fail(function () {
                    if (failFast) {
                        masterDeferred.reject();
                    } else {
                        hasFailed = true;
                    }
                });
                itemPromise.always(function () {
                    numCompleted++;
                    if (numCompleted === items.length) {
                        if (hasFailed) {
                            masterDeferred.reject();
                        } else {
                            masterDeferred.resolve();
                        }
                    }
                });
            });

        }

        return masterDeferred.promise();
    }
Executes a series of tasks in parallel, returning a "master" Promise that is resolved once
all the tasks have resolved. If one or more tasks fail, behavior depends on the failFast
flag:
  - If true, the master Promise is rejected as soon as the first task fails. The remaining
    tasks continue to completion in the background.
  - If false, the master Promise is rejected after all tasks have completed.

If nothing fails:          (M = master promise; 1-4 = tasks; d = done; F = fail)
 M  ------------d
 1 >---d        .
 2 >------d     .
 3 >---------d  .
 4 >------------d

With failFast = false:
 M  ------------F
 1 >---d     .  .
 2 >------d  .  .
 3 >---------F  .
 4 >------------d

With failFast = true: -- equivalent to $.when()
 M  ---------F
 1 >---d     .
 2 >------d  .
 3 >---------F
 4 >------------d   (#4 continues even though master Promise has failed)
(Note: if tasks finish synchronously, the behavior is more like failFast=false because you
won't get a chance to respond to the master Promise until after all items have been processed)

To perform task-specific work after an individual task completes, attach handlers to each
Promise before beginProcessItem() returns it.

Note: don't use this if individual tasks (or their done/fail handlers) could ever show a user-
visible dialog: because they run in parallel, you could show multiple dialogs atop each other.

@param {!Array.<*>} items
@param {!function(*, number):Promise} beginProcessItem
@param {!boolean} failFast
@return {$.Promise}