diff --git a/reviewboard/static/rb/js/common/collections/baseCollection.ts b/reviewboard/static/rb/js/common/collections/baseCollection.ts
index bc5238be80eb80a23a992545bc6c157fe101a01a..10af8aeccd956de9de84e8f0e750478db69c650d 100644
--- a/reviewboard/static/rb/js/common/collections/baseCollection.ts
+++ b/reviewboard/static/rb/js/common/collections/baseCollection.ts
@@ -34,33 +34,29 @@ export class BaseCollection<
      * Version Changed:
      *     5.0:
      *     This method was changed to return a promise. Using callbacks instead
-     *     of the promise is deprecated, and will be removed in Review Board
-     *     6.0.
+     *     of the promise is deprecated.
+     *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
      *
      * Args:
      *     options (object):
      *         Options for the fetch operation.
      *
-     *     context (object):
-     *         Context to be used when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the fetch operation is complete.
      */
     fetch(
         options: Backbone.CollectionFetchOptions = {},
-        context: object = undefined,
     ): Promise<BaseCollection> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error)) {
-            console.warn('RB.BaseCollection.fetch was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetch(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error),
+            dedent`
+                RB.BaseCollection.fetch was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         return new Promise((resolve, reject) => {
             super.fetch(_.defaults({
diff --git a/reviewboard/static/rb/js/common/resources/collections/repositoryCommitsCollection.ts b/reviewboard/static/rb/js/common/resources/collections/repositoryCommitsCollection.ts
index 866a7baac5d8694135a168c7b8486da91858d710..e0cd007205a9992f4338f2fb54697f3b1d6ff090 100644
--- a/reviewboard/static/rb/js/common/resources/collections/repositoryCommitsCollection.ts
+++ b/reviewboard/static/rb/js/common/resources/collections/repositoryCommitsCollection.ts
@@ -7,7 +7,6 @@ import {
     spina,
 } from '@beanbag/spina';
 
-import { API } from '../../utils/apiUtils';
 import { BaseCollection } from '../../collections/baseCollection';
 import {
     type RepositoryCommitAttrs,
@@ -171,39 +170,27 @@ export class RepositoryCommits extends BaseCollection<
      *     5.0:
      *     Added the promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for fetching the next page of results.
      *
-     *     context (object, optional):
-     *         Context to use when calling callbacks.
-     *
-     * Option Args:
-     *     error (function):
-     *         A function to call if fetching a page fails. This must take
-     *         ``collection, xhr`` arguments.
-     *
-     *     success (function):
-     *         A function to call if fetching a page succeeds.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async fetchNext(
-        options={},
-        context=undefined,
+        options: Backbone.PersistenceOptions = {},
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.RepositoryCommits.fetchNext was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetchNext(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.RepositoryCommits.fetchNext was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of promises.
+            `);
 
         if (this.canFetchNext()) {
             this.options.start = this.#nextStart;
diff --git a/reviewboard/static/rb/js/common/resources/collections/resourceCollection.ts b/reviewboard/static/rb/js/common/resources/collections/resourceCollection.ts
index 308060f8b865cfc1e2e120991d0ebe044bfe053f..88d05fc63925426d87d2b31d341170b8177322f1 100644
--- a/reviewboard/static/rb/js/common/resources/collections/resourceCollection.ts
+++ b/reviewboard/static/rb/js/common/resources/collections/resourceCollection.ts
@@ -5,7 +5,6 @@
 import { spina } from '@beanbag/spina';
 
 import { BaseCollection } from '../../collections/baseCollection';
-import { API } from '../../utils/apiUtils';
 import {
     type ResourceLink,
     BaseResource,
@@ -227,27 +226,23 @@ export class ResourceCollection<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (ResourceCollectionFetchOptions):
      *         Options for the fetch operation.
-     *
-     *     context (object):
-     *         Context to be used when calling callbacks.
      */
     async fetch(
         options: ResourceCollectionFetchOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ResourceCollection.fetch was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetch(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+    dedent`
+                RB.ResourceCollection.fetch was called using callbacks. This
+                has been removed in Review Board 8.0 in favor of promises.
+            `);
 
         const data = _.extend({}, options.data);
 
@@ -317,30 +312,27 @@ export class ResourceCollection<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (ResourceCollectionFetchOptions):
      *         Options for the fetch operation.
      *
-     *     context (object):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     fetchPrev(
         options: ResourceCollectionFetchOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ResourceCollection.fetchPrev was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetchPrev(newOptions));
+        if (options.success || options.error || options.complete) {
+            console.error(dedent`
+                RB.ResourceCollection.fetchPrev was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of
+                promises.
+            `);
         }
 
         if (!this.hasPrev) {
@@ -369,30 +361,27 @@ export class ResourceCollection<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (ResourceCollectionFetchOptions):
      *         Options for the fetch operation.
      *
-     *     context (object):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     fetchNext(
         options: ResourceCollectionFetchOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ResourceCollection.fetchNext was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetchNext(newOptions));
+        if (options.success || options.error || options.complete) {
+            console.error(dedent`
+                RB.ResourceCollection.fetchNext was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of
+                promises.
+            `);
         }
 
         if (!this.hasNext && options.enforceHasNext !== false) {
@@ -423,30 +412,27 @@ export class ResourceCollection<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (ResourceCollectionFetchOptions):
      *         Options for the fetch operation.
      *
-     *     context (object):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async fetchAll(
         options: ResourceCollectionFetchOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ResourceCollection.fetchNext was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetchAll(newOptions));
+        if (options.success || options.error || options.complete) {
+            console.error(dedent`
+                RB.ResourceCollection.fetchNext was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of
+                promises.
+            `);
         }
 
         const fetchOptions = _.defaults({
diff --git a/reviewboard/static/rb/js/common/resources/collections/tests/resourceCollectionTests.ts b/reviewboard/static/rb/js/common/resources/collections/tests/resourceCollectionTests.ts
index 4aef147af2437006fde4adc3dc562807479dd2c4..b7e275f688a00a326705f1da98364b0325fd3aec 100644
--- a/reviewboard/static/rb/js/common/resources/collections/tests/resourceCollectionTests.ts
+++ b/reviewboard/static/rb/js/common/resources/collections/tests/resourceCollectionTests.ts
@@ -15,9 +15,9 @@ import {
 } from 'reviewboard/common';
 
 
-suite('rb/resources/collections/ResourceCollection', function() {
-    let collection;
-    let reviewRequest;
+suite('rb/common/collections/ResourceCollection', function() {
+    let collection: ResourceCollection;
+    let reviewRequest: ReviewRequest;
 
     beforeEach(function() {
         reviewRequest = new ReviewRequest({
@@ -118,24 +118,6 @@ suite('rb/resources/collections/ResourceCollection', function() {
                         .toHaveBeenCalled();
                 });
             });
-
-            it('Using callbacks', function(done) {
-                spyOn(BaseCollection.prototype, 'fetch')
-                    .and.resolveTo();
-                spyOn(console, 'warn');
-
-                collection.fetch({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(reviewRequest.ready).toHaveBeenCalled();
-                        expect(BaseCollection.prototype.fetch)
-                            .toHaveBeenCalled();
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('fetchAll', function() {
@@ -225,101 +207,6 @@ suite('rb/resources/collections/ResourceCollection', function() {
                 expect(collection.at(2).id).toBe(3);
                 expect(collection.at(3).id).toBe(4);
             });
-
-            it('With callbacks', function(done) {
-                let numFetches = 0;
-
-                spyOn($, 'ajax').and.callFake(request => {
-                    console.assert(numFetches < 2);
-
-                    expect(request.type).toBe('GET');
-
-                    numFetches++;
-
-                    if (numFetches === 1) {
-                        expect(request.url).toBe(
-                            '/api/review-requests/123/reviews/');
-
-                        request.success({
-                            links: {
-                                next: {
-                                    href: '/api/review-requests/123/reviews/' +
-                                          '?start=25',
-                                    method: 'GET',
-                                },
-                                self: {
-                                    href: '/api/review-requests/123/reviews/',
-                                    method: 'GET',
-                                },
-                            },
-                            reviews: [
-                                {
-                                    id: 1,
-                                    links: {},
-                                },
-                                {
-                                    id: 2,
-                                    links: {},
-                                },
-                            ],
-                            stat: 'ok',
-                            total_results: 4,
-                        });
-                    } else if (numFetches === 2) {
-                        expect(request.url).toBe(
-                            '/api/review-requests/123/reviews/?start=25');
-
-                        request.success({
-                            links: {
-                                prev: {
-                                    href: '/api/review-requests/123/reviews/' +
-                                          '?start=0',
-                                    method: 'GET',
-                                },
-                                self: {
-                                    href: '/api/review-requests/123/reviews/' +
-                                          '?start=25',
-                                    method: 'GET',
-                                },
-                            },
-                            reviews: [
-                                {
-                                    id: 3,
-                                    links: {},
-                                },
-                                {
-                                    id: 4,
-                                    links: {},
-                                },
-                            ],
-                            stat: 'ok',
-                            total_results: 4,
-                        });
-                    }
-                });
-
-                spyOn(console, 'warn');
-
-                collection.fetchAll({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect($.ajax).toHaveBeenCalled();
-                        expect(numFetches).toBe(2);
-                        expect(collection.hasPrev).toBe(false);
-                        expect(collection.hasNext).toBe(false);
-                        expect(collection.totalResults).toBe(4);
-                        expect(collection.currentPage).toBe(0);
-                        expect(collection.length).toBe(4);
-                        expect(collection.at(0).id).toBe(1);
-                        expect(collection.at(1).id).toBe(2);
-                        expect(collection.at(2).id).toBe(3);
-                        expect(collection.at(3).id).toBe(4);
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('fetchNext', function() {
@@ -389,71 +276,6 @@ suite('rb/resources/collections/ResourceCollection', function() {
                 expect(collection.currentPage).toBe(3);
                 expect(collection.models.length).toBe(2);
             });
-
-            it('With callbacks', function(done) {
-                spyOn($, 'ajax').and.callFake(request => {
-                    expect(request.url).toBe(
-                        '/api/review-requests/123/reviews/?start=25');
-                    expect(request.type).toBe('GET');
-
-                    request.success({
-                        links: {
-                            next: {
-                                href: '/api/review-requests/123/reviews/' +
-                                      '?start=50',
-                                method: 'GET',
-                            },
-                            prev: {
-                                href: '/api/review-requests/123/reviews/' +
-                                      '?start=0',
-                                method: 'GET',
-                            },
-                            self: {
-                                href: '/api/review-requests/123/reviews/',
-                                method: 'GET',
-                            },
-                        },
-                        reviews: [
-                            {
-                                id: 1,
-                                links: {},
-                            },
-                            {
-                                id: 2,
-                                links: {},
-                            },
-                        ],
-                        stat: 'ok',
-                        total_results: 2,
-                    });
-                });
-
-                collection.hasNext = true;
-                collection.currentPage = 2;
-                collection._links = {
-                    next: {
-                        href: '/api/review-requests/123/reviews/?start=25',
-                        method: 'GET',
-                    },
-                };
-
-                spyOn(collection, 'fetch').and.callThrough();
-                spyOn(console, 'warn');
-
-                collection.fetchNext({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(collection.fetch).toHaveBeenCalled();
-                        expect(collection.hasPrev).toBe(true);
-                        expect(collection.hasNext).toBe(true);
-                        expect(collection.currentPage).toBe(3);
-                        expect(collection.models.length).toBe(2);
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('fetchPrev', function() {
@@ -523,71 +345,6 @@ suite('rb/resources/collections/ResourceCollection', function() {
                 expect(collection.currentPage).toBe(1);
                 expect(collection.models.length).toBe(2);
             });
-
-            it('With callbacks', function(done) {
-                spyOn($, 'ajax').and.callFake(request => {
-                    expect(request.url).toBe(
-                        '/api/review-requests/123/reviews/?start=25');
-                    expect(request.type).toBe('GET');
-
-                    request.success({
-                        links: {
-                            next: {
-                                href: '/api/review-requests/123/reviews/' +
-                                      '?start=25',
-                                method: 'GET',
-                            },
-                            prev: {
-                                href: '/api/review-requests/123/reviews/' +
-                                      '?start=0',
-                                method: 'GET',
-                            },
-                            self: {
-                                href: '/api/review-requests/123/reviews/',
-                                method: 'GET',
-                            },
-                        },
-                        reviews: [
-                            {
-                                id: 1,
-                                links: {},
-                            },
-                            {
-                                id: 2,
-                                links: {},
-                            },
-                        ],
-                        stat: 'ok',
-                        total_results: 2,
-                    });
-                });
-
-                collection.hasPrev = true;
-                collection.currentPage = 2;
-                collection._links = {
-                    prev: {
-                        href: '/api/review-requests/123/reviews/?start=25',
-                        method: 'GET',
-                    },
-                };
-
-                spyOn(collection, 'fetch').and.callThrough();
-                spyOn(console, 'warn');
-
-                collection.fetchPrev({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(collection.fetch).toHaveBeenCalled();
-                        expect(collection.hasPrev).toBe(true);
-                        expect(collection.hasNext).toBe(true);
-                        expect(collection.currentPage).toBe(1);
-                        expect(collection.models.length).toBe(2);
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('parse', function() {
diff --git a/reviewboard/static/rb/js/common/resources/models/baseResourceModel.ts b/reviewboard/static/rb/js/common/resources/models/baseResourceModel.ts
index 24e564d0e07cb3e35ed16de7ede270a4304ddf59..64151e8cbab70caa90d68ba78e2f3fc780fad79b 100644
--- a/reviewboard/static/rb/js/common/resources/models/baseResourceModel.ts
+++ b/reviewboard/static/rb/js/common/resources/models/baseResourceModel.ts
@@ -361,31 +361,28 @@ export class BaseResource<
      *     5.0:
      *     Deprecated callbacks and changed to return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (ReadyOptions):
      *         Options for the fetch operation.
      *
-     *     context (object):
-     *         Context to bind when executing callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async ready(
         options: ReadyOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete) ||
-            _.isFunction(options.ready)) {
-            console.warn('BaseResource.ready was called using callbacks. ' +
-                         'Callers should be updated to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, () => this.ready());
-        }
+        console.assert(
+            !(options.success || options.error || options.complete ||
+                         options.ready),
+            dedent`
+                BaseResource.ready was called using callbacks. This has been
+                removed in Review Board 8.0 in favor of promises.
+            `);
 
         const parentObject = this.get('parentObject');
 
@@ -418,31 +415,27 @@ export class BaseResource<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Object with success and error callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when executing callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async ensureCreated(
         options: Backbone.PersistenceOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('BaseResource.ensureCreated was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, () => this.ensureCreated());
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                BaseResource.ensureCreated was called using callbacks. This
+                has been removed in Review Board 8.0 in favor of promises.
+            `);
 
         await this.ready();
 
@@ -468,30 +461,27 @@ export class BaseResource<
      *     5.0:
      *     Deprecated the callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options to pass through to the base Backbone fetch operation.
      *
-     *     context (object, optional):
-     *         Context to bind when executing callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async fetch(
         options: Backbone.ModelFetchOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('BaseResource.fetch was called using callbacks. ' +
-                         'Callers should be updated to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.fetch(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                BaseResource.fetch was called using callbacks. This has been
+                removed in Review Board 8.0 in favor of promises.
+            `);
 
         if (this.isNew()) {
             throw new Error(
@@ -538,32 +528,29 @@ export class BaseResource<
      *     5.0:
      *     Deprecated the callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the save operation.
      *
-     *     context (object, optional):
-     *         Context to bind when executing callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async save(
         options: SaveOptions = {},
-        context: object = undefined,
     ): Promise<JQuery.jqXHR> {
         options ??= {};
 
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('BaseResource.save was called using callbacks. ' +
-                         'Callers should be updated to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.save(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                BaseResource.save was called using callbacks. This has been
+                removed in Review Board 8.0 in favor of promises.
+            `);
 
         this.trigger('saving', options);
         await this.ready();
@@ -732,31 +719,27 @@ export class BaseResource<
      *     5.0:
      *     Deprecated callbacks and changed to return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Object with success and error callbacks.
      *
-     *     context (object, optional):
-     *         Context to use when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async destroy(
         options: Backbone.ModelDestroyOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('BaseResource.destroy was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.destroy(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                BaseResource.destroy was called using callbacks. This has been
+                removed in Review Board 8.0 in favor of promises.
+            `);
 
         this.trigger('destroying', options);
 
diff --git a/reviewboard/static/rb/js/common/resources/models/draftResourceChildModelMixin.ts b/reviewboard/static/rb/js/common/resources/models/draftResourceChildModelMixin.ts
index ffc85c8c7032995f2e1b2721f091aec4f955e356..a8c7677d45bcca5f32490a3d5abaa5ad3b0c55ba 100644
--- a/reviewboard/static/rb/js/common/resources/models/draftResourceChildModelMixin.ts
+++ b/reviewboard/static/rb/js/common/resources/models/draftResourceChildModelMixin.ts
@@ -1,6 +1,5 @@
 /** Mixin for resources that are children of a draft resource. */
 
-import { API } from '../../utils/apiUtils';
 import { type ReadyOptions } from './baseResourceModel';
 
 
@@ -17,30 +16,32 @@ export const DraftResourceChildModelMixin = {
      * This will ensure the draft is created before deleting the object,
      * in order to record the deletion as part of the draft.
      *
+     * Version Changed:
+     *     5.0:
+     *     Deprecated callbacks and changed to return a promise.
+     *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async destroy(
         options: Backbone.ModelDestroyOptions = {},
-        context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.DraftResourceChildModelMixin.destroy was ' +
-                         'called using callbacks. Callers should be updated ' +
-                         'to use promises instead.');
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.destroy(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.DraftResourceChildModelMixin.destroy was called using
+                callbacks. This has been removed in Review Board 8.0 in favor
+                of promises.
+            `);
 
         await this.get('parentObject').ensureCreated();
         await _super(this).destroy.call(this, options);
@@ -56,13 +57,14 @@ export const DraftResourceChildModelMixin = {
      *     5.0:
      *     Deprecated callbacks and changed to return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
@@ -71,18 +73,16 @@ export const DraftResourceChildModelMixin = {
         options: ReadyOptions = {},
         context: object = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete) ||
-            _.isFunction(options.ready)) {
-            console.warn('RB.DraftResourceChildModelMixin.ready was ' +
-                         'called using callbacks. Callers should be updated ' +
-                         'to use promises instead.');
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.ready());
-        }
+        console.assert(
+            !(options.success || options.error || options.complete ||
+              options.ready),
+            dedent`
+                RB.DraftResourceChildModelMixin.ready was called using
+                callbacks. This has been removed in Review Board 8.0 in favor
+                of promises.
+            `);
 
         await this.get('parentObject').ensureCreated();
         await _super(this).ready.call(this);
-    }
+    },
 };
diff --git a/reviewboard/static/rb/js/common/resources/models/draftResourceModelMixin.ts b/reviewboard/static/rb/js/common/resources/models/draftResourceModelMixin.ts
index 024ec3f936143ca85c4431fece31021e131bca2d..84e79999c19d59e15b30fe09d01d99108acb5452 100644
--- a/reviewboard/static/rb/js/common/resources/models/draftResourceModelMixin.ts
+++ b/reviewboard/static/rb/js/common/resources/models/draftResourceModelMixin.ts
@@ -3,10 +3,7 @@
  */
 
 import { UserSession } from '../../models/userSessionModel';
-import {
-    API,
-    BackboneError,
-} from '../../utils/apiUtils';
+import { BackboneError } from '../../utils/apiUtils';
 
 
 /**
@@ -45,32 +42,29 @@ export const DraftResourceModelMixin = {
      *     5.0:
      *     Deprecated callbacks and changed to return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async ready(
         options: ReadyOptions = {},
-        context: unknown = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete) ||
-            _.isFunction(options.ready)) {
-            console.warn('RB.DraftResourceModelMixin.ready was ' +
-                         'called using callbacks. Callers should be updated ' +
-                         'to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.ready(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete ||
+              options.ready),
+            dedent`
+                RB.DraftResourceModelMixin.ready was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of
+                promises.
+            `);
 
         if (!this.get('loaded') && this.isNew() &&
             this._needDraft === undefined) {
@@ -99,31 +93,28 @@ export const DraftResourceModelMixin = {
      *     5.0:
      *     Deprecated callbacks and changed to return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async destroy(
         options: Backbone.ModelDestroyOptions = {},
-        context: unknown = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.DraftResourceModelMixin.destroy was ' +
-                         'called using callbacks. Callers should be updated ' +
-                         'to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.destroy(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.DraftResourceModelMixin.destroy was called using callbacks.
+                This has been removed in Review Board 8.0 in favor of
+                promises.
+            `);
 
         await this.ready();
         await _super(this).destroy.call(this, options);
diff --git a/reviewboard/static/rb/js/common/resources/models/draftReviewModel.ts b/reviewboard/static/rb/js/common/resources/models/draftReviewModel.ts
index 98709fb587b47841312c119cf7eaaf31661717bb..66009d1904d63b8e2a508847dc444432f29ed227 100644
--- a/reviewboard/static/rb/js/common/resources/models/draftReviewModel.ts
+++ b/reviewboard/static/rb/js/common/resources/models/draftReviewModel.ts
@@ -7,7 +7,6 @@ import {
     spina,
 } from '@beanbag/spina';
 
-import { API } from '../../utils/apiUtils';
 import * as JSONSerializers from '../utils/serializers';
 import {
     type SerializerMap,
@@ -95,30 +94,27 @@ export class DraftReview extends Review<
      *     5.0:
      *     Deprecated the callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async publish(
         options: SaveOptions = {},
-        context: object = undefined,
     ) {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn(`RB.DraftReview.publish was called using callbacks.
-                          Callers should be updated to use promises instead.`);
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.publish(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.DraftReview.publish was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         this.trigger('publishing');
 
diff --git a/reviewboard/static/rb/js/common/resources/models/draftReviewRequestModel.ts b/reviewboard/static/rb/js/common/resources/models/draftReviewRequestModel.ts
index ad98293832cea3d4575cdb5eaa4fe5ea8d08e771..2aac90b2d1c39dd1a5c3f6af6a74cb471d30c7b8 100644
--- a/reviewboard/static/rb/js/common/resources/models/draftReviewRequestModel.ts
+++ b/reviewboard/static/rb/js/common/resources/models/draftReviewRequestModel.ts
@@ -7,10 +7,7 @@ import {
     spina,
 } from '@beanbag/spina';
 
-import {
-    API,
-    BackboneError,
-} from '../../utils/apiUtils';
+import { BackboneError } from '../../utils/apiUtils';
 import {
     type BaseResourceAttrs,
     type BaseResourceResourceData,
@@ -235,31 +232,27 @@ export class DraftReviewRequest extends BaseResource<
      *     5.0:
      *     Deprecated the callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async publish(
         options: PublishOptions = {},
-        context: unknown = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn(
-                'RB.DraftReview.publish was called using callbacks. ' +
-                'Callers should be updated to use promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.publish(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.DraftReview.publish was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         await this.ready();
 
diff --git a/reviewboard/static/rb/js/common/resources/models/reviewGroupModel.ts b/reviewboard/static/rb/js/common/resources/models/reviewGroupModel.ts
index c7435c44f0de891bbb6e4879c3d26cac75c3f4d3..b13c8002f3746ec2fd8a8dfa7d9fb769de47c0a2 100644
--- a/reviewboard/static/rb/js/common/resources/models/reviewGroupModel.ts
+++ b/reviewboard/static/rb/js/common/resources/models/reviewGroupModel.ts
@@ -7,10 +7,7 @@ import {
     spina,
 } from '@beanbag/spina';
 
-import {
-    API,
-    BackboneError,
-} from '../../utils/apiUtils';
+import { BackboneError } from '../../utils/apiUtils';
 import {
     type BaseResourceAttrs,
     type BaseResourceResourceData,
@@ -185,6 +182,10 @@ export class ReviewGroup extends BaseResource<
      *     Deprecated the options and context parameters and changed to return
      *     a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     starred (boolean):
      *         Whether or not the group is starred.
@@ -192,9 +193,6 @@ export class ReviewGroup extends BaseResource<
      *     options (object, optional):
      *         Additional options for the save operation, including callbacks.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
@@ -202,18 +200,13 @@ export class ReviewGroup extends BaseResource<
     setStarred(
         starred: boolean,
         options: Backbone.ModelSaveOptions = {},
-        context: unknown = undefined,
     ): Promise<void | JQueryXHR> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ReviewGroup.setStarred was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.setStarred(starred));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.ReviewGroup.setStarred was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         const watched = UserSession.instance.watchedGroups;
 
@@ -231,6 +224,10 @@ export class ReviewGroup extends BaseResource<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     username (string):
      *         The username of the new user.
@@ -238,9 +235,6 @@ export class ReviewGroup extends BaseResource<
      *     options (object, optional):
      *         Additional options for the save operation.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
@@ -248,18 +242,13 @@ export class ReviewGroup extends BaseResource<
     addUser(
         username: string,
         options: Backbone.ModelSaveOptions = {},
-        context: unknown = undefined,
     ): Promise<JQueryXHR> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ReviewGroup.addUser was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.addUser(username));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.ReviewGroup.addUser was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         const url = this.url() + 'users/';
 
@@ -287,6 +276,10 @@ export class ReviewGroup extends BaseResource<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     username (string):
      *         The username of the new user.
@@ -294,9 +287,6 @@ export class ReviewGroup extends BaseResource<
      *     options (object, optional):
      *         Additional options for the save operation.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
@@ -304,18 +294,13 @@ export class ReviewGroup extends BaseResource<
     removeUser(
         username: string,
         options: Backbone.ModelSaveOptions = {},
-        context: unknown = undefined,
     ): Promise<void> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ReviewGroup.removeUser was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.removeUser(username));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.ReviewGroup.removeUser was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         const url = this.url() + 'users/';
 
diff --git a/reviewboard/static/rb/js/common/resources/models/reviewReplyModel.ts b/reviewboard/static/rb/js/common/resources/models/reviewReplyModel.ts
index 165efd8734df2babe5a10fed366e728bec713549..37d0ba6b576541d2fdf00ce94819252384f054f7 100644
--- a/reviewboard/static/rb/js/common/resources/models/reviewReplyModel.ts
+++ b/reviewboard/static/rb/js/common/resources/models/reviewReplyModel.ts
@@ -219,31 +219,27 @@ export class ReviewReply extends BaseResource<
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the save operation.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
     async publish(
         options: ReviewReplyPublishOptions = {},
-        context: unknown = undefined,
     ): Promise<void> {
-        if (typeof options.success === 'function' ||
-            typeof options.error === 'function' ||
-            typeof options.complete === 'function') {
-            console.warn('RB.ReviewReply.publish was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks<void>(options, context, newOptions =>
-                this.publish(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.ReviewReply.publish was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         this.trigger('publishing');
 
@@ -276,13 +272,14 @@ export class ReviewReply extends BaseResource<
      *     5.0:
      *     Changed to deprecate options and return a promise.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
      *         Options for the save operation.
      *
-     *     context (object, optional):
-     *         Context to bind when calling callbacks.
-     *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete. The
@@ -290,18 +287,13 @@ export class ReviewReply extends BaseResource<
      */
     async discardIfEmpty(
         options: Backbone.PersistenceOptions = {},
-        context: unknown = undefined,
     ): Promise<boolean> {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.ReviewReply.discardIfEmpty was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(options, context, newOptions =>
-                this.discardIfEmpty(newOptions));
-        }
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.ReviewReply.discardIfEmpty was called using callbacks. This
+                has been removed in Review Board 8.0 in favor of promises.
+            `);
 
         await this.ready();
 
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/baseResourceModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/baseResourceModelTests.ts
index bc5f698791b793951b0fdeb9b506034cb3cb233d..ba52ca1cba152033fb18396dd685b740585ad4a0 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/baseResourceModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/baseResourceModelTests.ts
@@ -71,26 +71,6 @@ suite('rb/resources/models/BaseResource', function() {
             expect(model.fetch).toHaveBeenCalled();
             expect(model.save).toHaveBeenCalled();
         });
-
-        it('With callbacks', function(done) {
-            model.set({
-                id: 1,
-                loaded: false,
-            });
-            spyOn(console, 'warn');
-
-            model.ensureCreated({
-                error: () => done.fail(),
-                success: () => {
-                    expect(model.ready).toHaveBeenCalled();
-                    expect(model.fetch).toHaveBeenCalled();
-                    expect(model.save).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
     });
 
     describe('fetch', function() {
@@ -151,28 +131,6 @@ suite('rb/resources/models/BaseResource', function() {
                 expect(Backbone.Model.prototype.fetch)
                     .not.toHaveBeenCalled();
             });
-
-            it('With callbacks', function(done) {
-                model.set({
-                    id: 123,
-                    parentObject: parentObject,
-                });
-
-                spyOn(parentObject, 'ready').and.resolveTo();
-                spyOn(console, 'warn');
-
-                model.fetch({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(parentObject.ready).toHaveBeenCalled();
-                        expect(Backbone.Model.prototype.fetch)
-                            .toHaveBeenCalled();
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('Response handling', function() {
@@ -335,25 +293,6 @@ suite('rb/resources/models/BaseResource', function() {
             await model.ready();
             expect(model.fetch).toHaveBeenCalled();
         });
-
-        it('With callbacks', function(done) {
-            model.set({
-                id: 123,
-                loaded: false,
-            });
-            expect(model.isNew()).toBe(false);
-            spyOn(console, 'warn');
-
-            model.ready({
-                error: () => done.fail(),
-                success: () => {
-                    expect(model.fetch).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
     });
 
     describe('save', function() {
@@ -493,43 +432,6 @@ suite('rb/resources/models/BaseResource', function() {
             expect(model.trigger).not.toHaveBeenCalledWith('saved');
         });
 
-        it('With callbacks', function(done) {
-            const responseData = {
-                foo: {},
-                stat: 'ok',
-            };
-
-            spyOn(parentObject, 'ensureCreated').and.resolveTo();
-            spyOn(parentObject, 'ready').and.resolveTo();
-
-            spyOn(Backbone.Model.prototype, 'save').and.callThrough();
-
-            model.set('parentObject', parentObject);
-
-            spyOn(API, 'call').and.callThrough();
-            spyOn($, 'ajax').and.callFake(request => {
-                expect(request.type).toBe('POST');
-
-                request.success(responseData);
-            });
-            spyOn(console, 'warn');
-
-            model.save({
-                error: () => done.fail(),
-                success: () => {
-                    expect(Backbone.Model.prototype.save).toHaveBeenCalled();
-                    expect(parentObject.ensureCreated).toHaveBeenCalled();
-                    expect(API.call).toHaveBeenCalled();
-                    expect($.ajax).toHaveBeenCalled();
-
-                    expect(model.trigger).toHaveBeenCalledWith('saved', {});
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
-
         describe('Request payload', function() {
             it('Saved data', async function() {
                 model.set('id', 1);
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/draftReviewModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/draftReviewModelTests.ts
index 73f70327eb598b2281785c4e1dcee38df1c78a95..859bcf5072f4fc8b426eaffc2476316263578be7 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/draftReviewModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/draftReviewModelTests.ts
@@ -65,24 +65,6 @@ suite('rb/resources/models/DraftReview', function() {
                 expect(parentObject.ready).toHaveBeenCalled();
                 expect(model._retrieveDraft).not.toHaveBeenCalled();
             });
-
-            it('With callbacks', function(done) {
-                expect(model.isNew()).toBe(true);
-                expect(model.get('loaded')).toBe(false);
-
-                spyOn(console, 'warn');
-
-                model.ready({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(parentObject.ready).toHaveBeenCalled();
-                        expect(model._retrieveDraft).toHaveBeenCalled();
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
 
         describe('publish', function() {
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/draftReviewRequestModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/draftReviewRequestModelTests.ts
index 2b180f1aa4d7983ad50d1cf53a6db405fad09ebc..c703261974c408e6d873ab218152da1faaeae754 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/draftReviewRequestModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/draftReviewRequestModelTests.ts
@@ -68,43 +68,6 @@ suite('rb/resources/models/DraftReviewRequest', function() {
             expect(API.call).toHaveBeenCalled();
             expect($.ajax).toHaveBeenCalled();
         });
-
-        it('With callbacks', function(done) {
-            spyOn(API, 'call').and.callThrough();
-            spyOn($, 'ajax').and.callFake(request => {
-                expect(request.data.public).toBe(1);
-
-                request.success({
-                    stat: 'ok',
-                    draft: {
-                        id: 1,
-                        links: {},
-                    },
-                });
-            });
-            spyOn(console, 'warn');
-
-            /* Set some fields in order to pass validation. */
-            draft.set({
-                targetGroups: [{
-                    name: 'mygroup',
-                    url: '/groups/mygroup',
-                }],
-                summary: 'My summary',
-                description: 'My description',
-            });
-
-            draft.publish({
-                success: () => {
-                    expect(API.call).toHaveBeenCalled();
-                    expect($.ajax).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-                error: () => done.fail(),
-            });
-        });
     });
 
     it('parse', function() {
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/reviewGroupModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/reviewGroupModelTests.ts
index de7346007ade8e1c2e94064ba7dd29bd23ce3a81..2534016a8ca721b150b15d47f627eea8c7bb4a4e 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/reviewGroupModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/reviewGroupModelTests.ts
@@ -72,31 +72,6 @@ suite('rb/resources/models/ReviewGroup', function() {
             expect(API.call).toHaveBeenCalled();
             expect($.ajax).toHaveBeenCalled();
         });
-
-        it('With callbacks', function(done) {
-            spyOn($, 'ajax').and.callFake(request => {
-                expect(request.type).toBe('POST');
-                expect(request.url).toBe(url);
-
-                request.success({
-                    stat: 'ok',
-                });
-            });
-            spyOn(console, 'warn');
-
-            group.setStarred(true, {
-                error: () => done.fail(),
-                success: () => {
-                    expect(session.watchedGroups.addImmediately)
-                        .toHaveBeenCalled();
-                    expect(API.call).toHaveBeenCalled();
-                    expect($.ajax).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
     });
 
     describe('addUser', function() {
@@ -126,29 +101,6 @@ suite('rb/resources/models/ReviewGroup', function() {
             expect($.ajax).toHaveBeenCalled();
         });
 
-        it('With callbacks', function(done) {
-            spyOn($, 'ajax').and.callFake(request => {
-                expect(request.type).toBe('POST');
-                expect(request.data.username).toBe('my-user');
-
-                request.success({
-                    stat: 'ok',
-                });
-            });
-            spyOn(console, 'warn');
-
-            group.addUser('my-user', {
-                error: () => done.fail(),
-                success: () => {
-                    expect(API.call).toHaveBeenCalled();
-                    expect($.ajax).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
-
         it('Unloaded group', async function() {
             spyOn($, 'ajax');
 
@@ -187,26 +139,6 @@ suite('rb/resources/models/ReviewGroup', function() {
             expect($.ajax).toHaveBeenCalled();
         });
 
-        it('With callbacks', function(done) {
-            spyOn($, 'ajax').and.callFake(request => {
-                expect(request.type).toBe('DELETE');
-
-                request.success();
-            });
-            spyOn(console, 'warn');
-
-            group.removeUser('my-user', {
-                error: () => done.fail(),
-                success: () => {
-                    expect(API.call).toHaveBeenCalled();
-                    expect($.ajax).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
-
         it('Unloaded group', async function() {
             spyOn($, 'ajax');
 
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/reviewReplyModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/reviewReplyModelTests.ts
index abb5cc7da5b220bc5c5d667fb1e94eecc726d6a6..0f8136a0a065ce31209ed03060d5f6310f920656 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/reviewReplyModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/reviewReplyModelTests.ts
@@ -214,21 +214,6 @@ suite('rb/resources/models/ReviewReply', function() {
                 expect(discarded).toBe(false);
                 expect(model.destroy).not.toHaveBeenCalled();
             });
-
-            it('With callbacks', function(done) {
-                spyOn(console, 'warn');
-
-                model.discardIfEmpty({
-                    error: () => done.fail(),
-                    success: discarded => {
-                        expect(discarded).toBe(true);
-                        expect(model.destroy).toHaveBeenCalled();
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
     });
 
@@ -319,31 +304,6 @@ suite('rb/resources/models/ReviewReply', function() {
             expect(Backbone.Model.prototype.fetch).toHaveBeenCalled();
             expect(model._needDraft).toBe(false);
         });
-
-        it('With callbacks', function(done) {
-            expect(model.isNew()).toBe(true);
-            expect(model.get('loaded')).toBe(false);
-
-            spyOn(Backbone.Model.prototype, 'fetch')
-                .and.callFake(options => {
-                    if (options && _.isFunction(options.success)) {
-                        options.success();
-                    }
-                });
-            spyOn(model, '_retrieveDraft').and.resolveTo();
-            spyOn(console, 'warn');
-
-            model.ready({
-                error: () => done.fail(),
-                success: () => {
-                    expect(parentObject.ready).toHaveBeenCalled();
-                    expect(model._retrieveDraft).toHaveBeenCalled();
-                    expect(console.warn).toHaveBeenCalled();
-
-                    done();
-                },
-            });
-        });
     });
 
     describe('parse', function() {
diff --git a/reviewboard/static/rb/js/common/resources/models/tests/reviewRequestModelTests.ts b/reviewboard/static/rb/js/common/resources/models/tests/reviewRequestModelTests.ts
index 7cb3a7a0395efe633a8ac57bf920da9d7ec5284e..8b856eb837a0592ae2d29d778abbfb7640920eba 100644
--- a/reviewboard/static/rb/js/common/resources/models/tests/reviewRequestModelTests.ts
+++ b/reviewboard/static/rb/js/common/resources/models/tests/reviewRequestModelTests.ts
@@ -289,36 +289,6 @@ suite('rb/resources/models/ReviewRequest', function() {
                 expect(API.call).toHaveBeenCalled();
                 expect($.ajax).toHaveBeenCalled();
             });
-
-            it('With callbacks', function(done) {
-                spyOn(API, 'call').and.callThrough();
-                spyOn($, 'ajax').and.callFake(request => {
-                    expect(request.type).toBe('PUT');
-                    expect(request.data.status).toBe('discarded');
-                    expect(request.data.description).toBe(undefined);
-
-                    request.success({
-                        review_request: {
-                            id: 1,
-                            links: {},
-                        },
-                        stat: 'ok',
-                    });
-                });
-                spyOn(console, 'warn');
-
-                reviewRequest.close({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(API.call).toHaveBeenCalled();
-                        expect($.ajax).toHaveBeenCalled();
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                    type: ReviewRequest.CLOSE_DISCARDED,
-                });
-            });
         });
     });
 });
diff --git a/reviewboard/static/rb/js/reviews/models/commentEditorModel.ts b/reviewboard/static/rb/js/reviews/models/commentEditorModel.ts
index b55835193622e40eb90a3589833b905581366edb..406319db56752439ee45d3f94e32f23f39a2c4ba 100644
--- a/reviewboard/static/rb/js/reviews/models/commentEditorModel.ts
+++ b/reviewboard/static/rb/js/reviews/models/commentEditorModel.ts
@@ -9,7 +9,6 @@ import {
 import {
     type BaseComment,
     type ReviewRequest,
-    API,
     ExtraDataMixin,
     UserSession,
 } from 'reviewboard/common';
@@ -247,33 +246,31 @@ export class CommentEditor extends BaseModel<CommentEditorAttrs> {
      *     5.0:
      *     Deprecated callbacks and added a promise return value.
      *
+     * Version Changed:
+     *     8.0:
+     *     Removed callbacks and the context parameter.
+     *
      * Args:
      *     options (object, optional):
-     *         Options for the save operation.
-     *
-     *     context (object, optional):
-     *         The context to use when calling callbacks.
+     *         Legacy options for the save operation.
      *
      * Returns:
      *     Promise:
      *     A promise which resolves when the operation is complete.
      */
-    async save(options={}, context=undefined) {
-        if (_.isFunction(options.success) ||
-            _.isFunction(options.error) ||
-            _.isFunction(options.complete)) {
-            console.warn('RB.CommentEditor.save was called using ' +
-                         'callbacks. Callers should be updated to use ' +
-                         'promises instead.');
-
-            return API.promiseToCallbacks(
-                options, context, newOptions => this.save(newOptions));
-        }
+    async save(options = {}) {
+        console.assert(
+            !(options.success || options.error || options.complete),
+            dedent`
+                RB.CommentEditor.save was called using callbacks. This has
+                been removed in Review Board 8.0 in favor of promises.
+            `);
 
         console.assert(this.get('canSave'),
                        'save() called when canSave is false.');
 
-        const extraData = _.clone(this.get('extraData'));
+        const extraData = <{ [key: string]: unknown }>_.clone(
+            this.get('extraData'));
         extraData.require_verification = this.get('requireVerification');
 
         const comment = this.get('comment');
diff --git a/reviewboard/static/rb/js/reviews/models/tests/commentEditorModelTests.ts b/reviewboard/static/rb/js/reviews/models/tests/commentEditorModelTests.ts
index 5902187e657278fee348dff76c6012e65ed479d8..6b71e4107db6f69e308326341a5c35a49c65c011 100644
--- a/reviewboard/static/rb/js/reviews/models/tests/commentEditorModelTests.ts
+++ b/reviewboard/static/rb/js/reviews/models/tests/commentEditorModelTests.ts
@@ -16,10 +16,10 @@ import {
 import { CommentEditor } from 'reviewboard/reviews';
 
 
-suite('rb/models/CommentEditor', function() {
-    let editor;
-    let reviewRequest;
-    let comment;
+suite('rb/reviews/models/CommentEditor', function() {
+    let editor: CommentEditor;
+    let reviewRequest: ReviewRequest;
+    let comment: BaseComment;
 
     function createComment() {
         return new BaseComment({
@@ -278,6 +278,7 @@ suite('rb/models/CommentEditor', function() {
                     comment: comment,
                 });
 
+                console.assert.calls.reset();
                 expect(() => editor.beginEdit()).toThrow();
                 expect(console.assert).toHaveBeenCalled();
                 expect(editor.get('canDelete')).toBe(false);
@@ -403,6 +404,7 @@ suite('rb/models/CommentEditor', function() {
                     comment: createComment(),
                 });
 
+                console.assert.calls.reset();
                 editor.beginEdit();
                 expect(console.assert.calls.argsFor(0)[0]).toBeTruthy();
             });
@@ -413,11 +415,13 @@ suite('rb/models/CommentEditor', function() {
                     comment: createComment(),
                 });
 
+                console.assert.calls.reset();
                 expect(function() { editor.beginEdit(); }).toThrow();
                 expect(console.assert.calls.argsFor(0)[0]).toBeFalsy();
             });
 
             it('With no comment', function() {
+                console.assert.calls.reset();
                 expect(function() { editor.beginEdit(); }).toThrow();
                 expect(console.assert.calls.argsFor(0)[0]).toBeTruthy();
                 expect(console.assert.calls.argsFor(1)[0]).toBeFalsy();
@@ -464,6 +468,7 @@ suite('rb/models/CommentEditor', function() {
                 editor.set('comment', comment);
                 editor.set('canDelete', false);
 
+                console.assert.calls.reset();
                 await expectAsync(editor.deleteComment()).toBeRejectedWith(
                     Error('deleteComment() called when canDelete is false.'));
                 expect(console.assert.calls.argsFor(0)[0]).toBeFalsy();
@@ -477,6 +482,7 @@ suite('rb/models/CommentEditor', function() {
                 editor.set('comment', comment);
                 editor.set('canDelete', true);
 
+                console.assert.calls.reset();
                 await editor.deleteComment();
                 expect(console.assert.calls.argsFor(0)[0]).toBeTruthy();
                 expect(comment.destroy).toHaveBeenCalled();
@@ -499,9 +505,10 @@ suite('rb/models/CommentEditor', function() {
                 editor.set('comment', comment);
                 editor.set('canSave', false);
 
+                console.assert.calls.reset();
                 await expectAsync(editor.save()).toBeRejectedWith(
                     Error('save() called when canSave is false.'));
-                expect(console.assert.calls.argsFor(0)[0]).toBeFalsy();
+                expect(console.assert.calls.argsFor(1)[0]).toBeFalsy();
                 expect(comment.save).not.toHaveBeenCalled();
                 expect(editor.trigger).not.toHaveBeenCalledWith('saved');
             });
@@ -521,6 +528,7 @@ suite('rb/models/CommentEditor', function() {
                 });
                 editor.setExtraData('mykey', 'myvalue');
 
+                console.assert.calls.reset();
                 await editor.save();
 
                 expect(console.assert.calls.argsFor(0)[0]).toBeTruthy();
@@ -535,45 +543,6 @@ suite('rb/models/CommentEditor', function() {
                 expect(editor.get('dirty')).toBe(false);
                 expect(editor.trigger).toHaveBeenCalledWith('saved');
             });
-
-            it('With callbacks', function(done) {
-                /* Set these in order, to override canSave. */
-                const text = 'My text';
-                const issueOpened = true;
-
-                comment.set('issueOpened', false);
-                editor.set('comment', comment);
-                editor.set({
-                    canSave: true,
-                    issue_opened: issueOpened,
-                    richText: true,
-                    text: text,
-                });
-                editor.setExtraData('mykey', 'myvalue');
-
-                spyOn(console, 'warn');
-
-                editor.save({
-                    error: () => done.fail(),
-                    success: () => {
-                        expect(console.assert.calls.argsFor(0)[0])
-                            .toBeTruthy();
-                        expect(comment.save).toHaveBeenCalled();
-                        expect(comment.get('text')).toBe(text);
-                        expect(comment.get('issueOpened')).toBe(issueOpened);
-                        expect(comment.get('richText')).toBe(true);
-                        expect(comment.get('extraData')).toEqual({
-                            mykey: 'myvalue',
-                            require_verification: false,
-                        });
-                        expect(editor.get('dirty')).toBe(false);
-                        expect(editor.trigger).toHaveBeenCalledWith('saved');
-                        expect(console.warn).toHaveBeenCalled();
-
-                        done();
-                    },
-                });
-            });
         });
     });
 });
