diff --git a/djblets/static/djblets/js/extensions/models/extensionHookModel.js b/djblets/static/djblets/js/extensions/models/extensionHookModel.js
index d3520f0c2e0f486a448963442cd1d038abaf7098..c5ff36b490a3cc2bf80d00ce8ad373ad16a7a8bf 100644
--- a/djblets/static/djblets/js/extensions/models/extensionHookModel.js
+++ b/djblets/static/djblets/js/extensions/models/extensionHookModel.js
@@ -65,5 +65,46 @@ Djblets.ExtensionHook = Backbone.Model.extend({
      */
     each: function(cb, context) {
         _.each(this.prototype.hookPoint.hooks, cb, context);
+    },
+
+
+    /**
+     * Loops through each registered hook instance and calls the given
+     * callback method, creating a promise that they all complete.
+     *
+     * Args:
+     *     cb (Function):
+     *         The callback method. This will be called with four arguments:
+     *              deffered (jQuery.Deffered):
+     *                  The deferred object that can be used to update the
+     *                  status of one iteration of the loop using its
+     *                  resolve() or reject() methods.
+     *
+     *              hook (Djblets.ExtensionHook):
+     *                  A hook instance in the collection.
+     *
+     *              index (Number):
+     *                  The index of the hook in the collection.
+     *
+     *              hooks (Array):
+     *                  An array of all the hooks in the collection.
+     *
+     *     context (Object):
+     *          The context to pass to the callback.
+     *
+     * Returns:
+     *     jQuery.Promise:
+     *     A promise that all the hooks' callback methods resolved.
+     */
+    eachAsync: function(cb, context) {
+        var deferredObjs = this.prototype.hookPoint.hooks.map(
+            function(hook, index, array) {
+                var deferred = $.Deferred();
+                cb.call(context, deferred, hook, index, array);
+                return deferred;
+            }
+        );
+
+        return $.when.apply($, deferredObjs);
     }
 });
