Repository: WP-API/node-wpapi
Pull request #428 (2019-05-28 13:26:35)

Breaking: Remove support for "Node-style" error-first callbacks
Our original HTTP transport `superagent` does not use Promises by default, so all versions of this library to date have supported Node-style error-first callbacks, _e.g._ `.get( ( err, data ) => { if ( err ) {} ... } )`. However, Promises are the preferred and recommended way of using this library, and the vast majority of library consumers do not utilize the callback method signature.

While evaluating additional transport providers, particularly `fetch`, it became obvious that continuing to support callback-style asynchronous methods would be cumbersome with Promise-aware, modern transport methods like `fetch` or libraries like `axios`. As part of the slimming-down of our API for Version 2, this PR removes support for the node-style callback signature on all transport methods.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2146b6..d42793d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,7 @@
 # Changelog
 
 ## v2.0.0 [**alpha**] _Second Toughest in the Infants_
-
+- **BREAKING**: "Node-style" error-first callbacks (_e.g._ `.get( ( err, data ) => {} )`) are no longer supported. All transport request methods return Promises.
 - **BREAKING**: The module exported as `wpapi` no longer includes HTTP methods. Install `superagent` as a peer dependency and `require( 'wpapi/superagent' )` in order to make HTTP requests.
 - **BREAKING**: Autodiscovery now either succeeds or fails; a WPAPI instance configured with default routes will no longer be returned.
 
@@ -11,13 +11,11 @@
 - Throw an error early when `.file()` is passed a Buffer object without an accompanying name string, props @cungminh2710 & @mvhirsch
 
 
-
 ## v1.2.1 _Colomb_
 
 - Fix issue where `li` was improperly declared as a dev-only dependency, props @el-lsan & @jerolan.
 
 
-
 ## v1.2.0 _Space Is Only Noise_
 
 - **BREAKING**: The minimum supported node version is now v8.6, or v8.2.1 with the `--harmony` flag.
diff --git a/README.md b/README.md
index 2e3573b..3668a03 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,8 @@ At present this browser bundle tracks the `wpapi/superagent` module, and include
 
 ### Upgrading from v1
 
+### Require `wpapi/superagent`
+
 Prior to version 2.0 (currently `alpha` status) this library shipped with built-in HTTP functionality using Superagent.
 
 If you maintain an existing project which uses this library and wish to upgrade to v2, you may do so by manually installing Superagent:
@@ -93,6 +95,21 @@ and then changing your `require` statements to use the `wpapi/superagent` entryp
 +++ const WPAPI = require( 'wpapi/superagent' );
 ```
 
+### Use Promises instead of Callbacks
+
+In version 1, you could use "Node-style" error-first callback functions instead of chaining promises. As of version 2, this callback style is no longer supported; use Promise `.then` syntax or [`async`/`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) instead.
+
+```js
+// Version 1
+wp.posts().get( function( error, posts ) { /* ... */ } );
+
+// Version 2, Promises syntax
+wp.posts().get().then( posts => { /* ... */ } );
+
+// Version 2, await syntax
+await wp.posts().get();
+```
+
 ## Using the Client
 
 The module is a constructor, so you can create an instance of the API client bound to the endpoint for your WordPress install:
@@ -103,23 +120,17 @@ var wp = new WPAPI({ endpoint: 'http://src.wordpress-develop.dev/wp-json' });
 ```
 Once an instance is constructed, you can chain off of it to construct a specific request. (Think of it as a query-builder for WordPress!)
 
-We support requesting posts using either a callback-style or promise-style syntax:
+**Compatibility Note:** As of Version 2.0, Node-style error-first callbacks are no longer supported by this library. All request methods return Promises.
 
 ```javascript
-// Callbacks
-wp.posts().get(function( err, data ) {
-    if ( err ) {
-        // handle err
-    }
-    // do something with the returned posts
-});
-
-// Promises
-wp.posts().then(function( data ) {
-    // do something with the returned posts
-}).catch(function( err ) {
-    // handle error
-});
+// Request methods return Promises.
+wp.posts().get()
+    .then(function( data ) {
+        // do something with the returned posts
+    })
+    .catch(function( err ) {
+        // handle error
+    });
 ```
 The `wp` object has endpoint handler methods for every endpoint that ships with the default WordPress REST API plugin.
 
@@ -798,7 +809,9 @@ By default `node-wpapi` uses the [superagent](https://www.npmjs.com/package/supe
 
 **This is advanced behavior; you will only need to utilize this functionality if your application has very specific HTTP handling or caching requirements.**
 
-In order to maintain consistency with the rest of the API, custom transport methods should take in a WordPress API route handler query object (_e.g._ the result of calling `wp.posts()...` or any of the other chaining resource handlers), a `data` object (for POST, PUT and DELETE requests), and an optional callback function (as `node-wpapi` transport methods both return Promise objects _and_ support traditional `function( err, response )` callbacks).
+In order to maintain consistency with the rest of the API, custom transport methods should take in a WordPress API route handler query object (_e.g._ the result of calling `wp.posts()...` or any of the other chaining resource handlers)and a `data` object (for POST, PUT and DELETE requests).
+
+**Note:** Node-style error-first callbacks are no longer supported by this library as of version 2.0. Custom transport methods should therefore not accept or expect a third optional callback parameter.
 
 The default HTTP transport methods are available as `WPAPI.transport` (a property of the constructor object) and may be called within your transports if you wish to extend the existing behavior, as in the example below.
 
@@ -809,16 +822,11 @@ var site = new WPAPI({
   endpoint: 'http://my-site.com/wp-json',
   transport: {
     // Only override the transport for the GET method, in this example
-    // Transport methods should take a wpreq object and a callback:
-    get: function( wpreq, cb ) {
+    // Transport methods should take a wpreq object:
+    get: function( wpreq ) {
       var result = cache[ wpreq ];
-      // If a cache hit is found, return it via the same callback/promise
-      // signature as the default transport method:
+      // If a cache hit is found, return it wrapped in a Promise:
       if ( result ) {
-        if ( cb && typeof cb === 'function' ) {
-          // Invoke the callback function, if one was provided
-          cb( null, result );
-        }
         // Return the data as a promise
         return Promise.resolve( result );
       }
@@ -837,8 +845,8 @@ You may set one or many custom HTTP transport methods on an existing WP site cli
 
 ```js
 site.transport({
-    get: function( wpreq, callbackFn ) { /* ... */},
-    put: function( wpreq, callbackFn ) { /* ... */}
+    get: function( wpreq ) { /* ... */},
+    put: function( wpreq, data ) { /* ... */}
 });
 ```
 
diff --git a/lib/bind-transport.js b/lib/bind-transport.js
new file mode 100644
index 0000000..2016d23
--- /dev/null
+++ b/lib/bind-transport.js
@@ -0,0 +1,13 @@
+/**
+ * Utility method for binding a frozen transport object to the WPAPI constructor
+ *
+ * See /axios and /superagent directories
+ * @param {Function} WPAPI         The WPAPI constructor
+ * @param {Object}   httpTransport The HTTP transport object
+ * @returns {Function} The WPAPI object augmented with the provided transport
+ */
+module.exports = function( WPAPI, httpTransport ) {
+	WPAPI.transport = Object.create( httpTransport );
+	Object.freeze( WPAPI.transport );
+	return WPAPI;
+};
diff --git a/lib/constructors/wp-request.js b/lib/constructors/wp-request.js
index 78eaeda..3fd9ed5 100644
--- a/lib/constructors/wp-request.js
+++ b/lib/constructors/wp-request.js
@@ -709,11 +709,10 @@ WPRequest.prototype.setHeaders = function( headers, value ) {
  *
  * @method
  * @async
- * @param {Function} [callback] A callback to invoke with the results of the GET request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-WPRequest.prototype.get = function( callback ) {
-	return this.transport.get( this, callback );
+WPRequest.prototype.get = function() {
+	return this.transport.get( this );
 };
 
 /**
@@ -721,11 +720,10 @@ WPRequest.prototype.get = function( callback ) {
  *
  * @method
  * @async
- * @param {Function} [callback] A callback to invoke with the results of the HEAD request
  * @returns {Promise} A promise to the header results of the HTTP request
  */
-WPRequest.prototype.headers = function( callback ) {
-	return this.transport.head( this, callback );
+WPRequest.prototype.headers = function() {
+	return this.transport.head( this );
 };
 
 /**
@@ -736,11 +734,10 @@ WPRequest.prototype.headers = function( callback ) {
  * @method
  * @async
  * @param {Object} data The data for the POST request
- * @param {Function} [callback] A callback to invoke with the results of the POST request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-WPRequest.prototype.create = function( data, callback ) {
-	return this.transport.post( this, data, callback );
+WPRequest.prototype.create = function( data ) {
+	return this.transport.post( this, data );
 };
 
 /**
@@ -752,11 +749,10 @@ WPRequest.prototype.create = function( data, callback ) {
  * @async
  * @private
  * @param {Object} data The data for the PUT request
- * @param {Function} [callback] A callback to invoke with the results of the PUT request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-WPRequest.prototype.update = function( data, callback ) {
-	return this.transport.put( this, data, callback );
+WPRequest.prototype.update = function( data ) {
+	return this.transport.put( this, data );
 };
 
 /**
@@ -765,11 +761,10 @@ WPRequest.prototype.update = function( data, callback ) {
  * @method
  * @async
  * @param {Object} [data] Data to send along with the DELETE request
- * @param {Function} [callback] A callback to invoke with the results of the DELETE request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-WPRequest.prototype.delete = function( data, callback ) {
-	return this.transport.delete( this, data, callback );
+WPRequest.prototype.delete = function( data ) {
+	return this.transport.delete( this, data );
 };
 
 /**
diff --git a/package.json b/package.json
index 9f91dbf..3345d68 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
       "**/tests/**/*.js"
     ],
     "testPathIgnorePatterns": [
+      "test.js",
       ".eslintrc.js",
       "/tests/helpers/"
     ],
diff --git a/superagent/index.js b/superagent/index.js
index 7aa1d70..1e105cb 100644
--- a/superagent/index.js
+++ b/superagent/index.js
@@ -1,19 +1,6 @@
 const WPAPI = require( '../wpapi' );
+const superagentTransport = require( './superagent-transport' );
+const bindTransport = require( '../lib/bind-transport' );
 
-// Pull in superagent-based HTTP transport
-const httpTransport = require( './http-transport' );
-
-/**
- * The HTTP transport methods object used by all WPAPI instances
- *
- * These methods may be extended or replaced on an instance-by-instance basis
- *
- * @memberof! WPAPI
- * @static
- * @property transport
- * @type {Object}
- */
-WPAPI.transport = Object.create( httpTransport );
-Object.freeze( WPAPI.transport );
-
-module.exports = WPAPI;
+// Bind the superagent-based HTTP transport to the WPAPI constructor
+module.exports = bindTransport( WPAPI, superagentTransport );
diff --git a/superagent/http-transport.js b/superagent/superagent-transport.js
similarity index 83%
rename from superagent/http-transport.js
rename to superagent/superagent-transport.js
index db65b20..c22dc43 100644
--- a/superagent/http-transport.js
+++ b/superagent/superagent-transport.js
@@ -179,16 +179,15 @@ function createPaginationObject( result, options, httpTransport ) {
 // ====================
 
 /**
- * Submit the provided superagent request object, invoke a callback (if it was
- * provided), and return a promise to the response from the HTTP request.
+ * Submit the provided superagent request object and return a promise which
+ * resolves to the response from the HTTP request.
  *
  * @private
  * @param {Object} request A superagent request object
- * @param {Function} callback A callback function (optional)
  * @param {Function} transform A function to transform the result data
  * @returns {Promise} A promise to the superagent request
  */
-function invokeAndPromisify( request, callback, transform ) {
+function invokeAndPromisify( request, transform ) {
 	return new Promise( ( resolve, reject ) => {
 		// Fire off the result
 		request.end( ( err, result ) => {
@@ -200,14 +199,7 @@ function invokeAndPromisify( request, callback, transform ) {
 				resolve( result );
 			}
 		} );
-	} ).then( transform ).then( ( result ) => {
-		// If a node-style callback was provided, call it, but also return the
-		// result value for use via the returned Promise
-		if ( callback && typeof callback === 'function' ) {
-			callback( null, result );
-		}
-		return result;
-	}, ( err ) => {
+	} ).then( transform ).catch( ( err ) => {
 		// If the API provided an error object, it will be available within the
 		// superagent response object as response.body (containing the response
 		// JSON). If that object exists, it will have a .code property if it is
@@ -217,13 +209,8 @@ function invokeAndPromisify( request, callback, transform ) {
 			// all transport-specific (superagent-specific) properties
 			err = err.response.body;
 		}
-		// If a callback was provided, ensure it is called with the error; otherwise
-		// re-throw the error so that it can be handled by a Promise .catch or .then
-		if ( callback && typeof callback === 'function' ) {
-			callback( err );
-		} else {
-			throw err;
-		}
+		// Re-throw the error so that it can be handled by a Promise .catch or .then
+		throw err;
 	} );
 }
 
@@ -264,17 +251,16 @@ function returnHeaders( result ) {
  * @method get
  * @async
  * @param {WPRequest} wpreq A WPRequest query object
- * @param {Function} [callback] A callback to invoke with the results of the GET request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-function _httpGet( wpreq, callback ) {
+function _httpGet( wpreq ) {
 	checkMethodSupport( 'get', wpreq );
 	const url = wpreq.toString();
 
 	let request = _auth( agent.get( url ), wpreq._options );
 	request = _setHeaders( request, wpreq._options );
 
-	return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
+	return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
 }
 
 /**
@@ -283,10 +269,9 @@ function _httpGet( wpreq, callback ) {
  * @async
  * @param {WPRequest} wpreq A WPRequest query object
  * @param {Object} data The data for the POST request
- * @param {Function} [callback] A callback to invoke with the results of the POST request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-function _httpPost( wpreq, data, callback ) {
+function _httpPost( wpreq, data ) {
 	checkMethodSupport( 'post', wpreq );
 	const url = wpreq.toString();
 	data = data || {};
@@ -304,7 +289,7 @@ function _httpPost( wpreq, data, callback ) {
 		request = request.send( data );
 	}
 
-	return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
+	return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
 }
 
 /**
@@ -312,10 +297,9 @@ function _httpPost( wpreq, data, callback ) {
  * @async
  * @param {WPRequest} wpreq A WPRequest query object
  * @param {Object} data The data for the PUT request
- * @param {Function} [callback] A callback to invoke with the results of the PUT request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-function _httpPut( wpreq, data, callback ) {
+function _httpPut( wpreq, data ) {
 	checkMethodSupport( 'put', wpreq );
 	const url = wpreq.toString();
 	data = data || {};
@@ -323,7 +307,7 @@ function _httpPut( wpreq, data, callback ) {
 	let request = _auth( agent.put( url ), wpreq._options, true ).send( data );
 	request = _setHeaders( request, wpreq._options );
 
-	return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
+	return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
 }
 
 /**
@@ -331,36 +315,30 @@ function _httpPut( wpreq, data, callback ) {
  * @async
  * @param {WPRequest} wpreq A WPRequest query object
  * @param {Object} [data] Data to send along with the DELETE request
- * @param {Function} [callback] A callback to invoke with the results of the DELETE request
  * @returns {Promise} A promise to the results of the HTTP request
  */
-function _httpDelete( wpreq, data, callback ) {
-	if ( ! callback && typeof data === 'function' ) {
-		callback = data;
-		data = null;
-	}
+function _httpDelete( wpreq, data ) {
 	checkMethodSupport( 'delete', wpreq );
 	const url = wpreq.toString();
 	let request = _auth( agent.del( url ), wpreq._options, true ).send( data );
 	request = _setHeaders( request, wpreq._options );
 
-	return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
+	return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
 }
 
 /**
  * @method head
  * @async
  * @param {WPRequest} wpreq A WPRequest query object
- * @param {Function} [callback] A callback to invoke with the results of the HEAD request
  * @returns {Promise} A promise to the header results of the HTTP request
  */
-function _httpHead( wpreq, callback ) {
+function _httpHead( wpreq ) {
 	checkMethodSupport( 'head', wpreq );
 	const url = wpreq.toString();
 	let request = _auth( agent.head( url ), wpreq._options );
 	request = _setHeaders( request, wpreq._options );
 
-	return invokeAndPromisify( request, callback, returnHeaders );
+	return invokeAndPromisify( request, returnHeaders );
 }
 
 module.exports = {
diff --git a/wpapi.js b/wpapi.js
index 7e8d10f..4fe18f7 100644
--- a/wpapi.js
+++ b/wpapi.js
@@ -102,19 +102,16 @@ function WPAPI( options ) {
  *
  *     // Overwrite the GET behavior to inject a caching layer
  *     site.transport({
- *       get: function( wpreq, cb ) {
+ *       get: function( wpreq ) {
  *         var result = cache[ wpreq ];
- *         // If a cache hit is found, return it via the same callback/promise
- *         // signature as the default transport method
+ *         // If a cache hit is found, return it via the same promise
+ *         // signature as that of the default transport method
  *         if ( result ) {
- *           if ( cb && typeof cb === 'function' ) {
- *             cb( null, result );
- *           }
  *           return Promise.resolve( result );
  *         }
  *
  *         // Delegate to default transport if no cached data was found
- *         return WPAPI.transport.get( wpreq, cb ).then(function( result ) {
+ *         return WPAPI.transport.get( wpreq ).then(function( result ) {
  *           cache[ wpreq ] = result;
  *           return result;
  *         });


Test patch:
diff --git a/superagent/tests/unit/superagent.js b/superagent/tests/unit/superagent.js
index e1339b6..c7703ef 100644
--- a/superagent/tests/unit/superagent.js
+++ b/superagent/tests/unit/superagent.js
@@ -3,7 +3,7 @@
 const WPAPI = require( '../../' );
 
 // HTTP transport, for stubbing
-const httpTransport = require( '../../http-transport' );
+const superagentTransport = require( '../../superagent-transport' );
 
 // Variable to use as our "success token" in promise assertions
 const SUCCESS = 'success';
@@ -15,54 +15,54 @@ describe( 'WPAPI', () => {
 		describe( 'assigns default HTTP transport', () => {
 
 			it( 'for GET requests', () => {
-				jest.spyOn( httpTransport, 'get' ).mockImplementation( () => {} );
+				jest.spyOn( superagentTransport, 'get' ).mockImplementation( () => {} );
 				const site = new WPAPI( {
 					endpoint: 'http://some.url.com/wp-json',
 				} );
 				const query = site.root( '' );
 				query.get();
-				expect( httpTransport.get ).toHaveBeenCalledWith( query, undefined );
-				httpTransport.get.mockRestore();
+				expect( superagentTransport.get ).toHaveBeenCalledWith( query );
+				superagentTransport.get.mockRestore();
 			} );
 
 			it( 'for POST requests', () => {
-				jest.spyOn( httpTransport, 'post' ).mockImplementation( () => {} );
+				jest.spyOn( superagentTransport, 'post' ).mockImplementation( () => {} );
 				const site = new WPAPI( {
 					endpoint: 'http://some.url.com/wp-json',
 				} );
 				const query = site.root( '' );
 				const data = {};
 				query.create( data );
-				expect( httpTransport.post ).toHaveBeenCalledWith( query, data, undefined );
-				httpTransport.post.mockRestore();
+				expect( superagentTransport.post ).toHaveBeenCalledWith( query, data );
+				superagentTransport.post.mockRestore();
 			} );
 
 			it( 'for POST requests', () => {
-				jest.spyOn( httpTransport, 'post' ).mockImplementation( () => {} );
+				jest.spyOn( superagentTransport, 'post' ).mockImplementation( () => {} );
 				const site = new WPAPI( {
 					endpoint: 'http://some.url.com/wp-json',
 				} );
 				const query = site.root( '' );
 				const data = {};
 				query.create( data );
-				expect( httpTransport.post ).toHaveBeenCalledWith( query, data, undefined );
-				httpTransport.post.mockRestore();
+				expect( superagentTransport.post ).toHaveBeenCalledWith( query, data );
+				superagentTransport.post.mockRestore();
 			} );
 
 			it( 'for PUT requests', () => {
-				jest.spyOn( httpTransport, 'put' ).mockImplementation( () => {} );
+				jest.spyOn( superagentTransport, 'put' ).mockImplementation( () => {} );
 				const site = new WPAPI( {
 					endpoint: 'http://some.url.com/wp-json',
 				} );
 				const query = site.root( 'a-resource' );
 				const data = {};
 				query.update( data );
-				expect( httpTransport.put ).toHaveBeenCalledWith( query, data, undefined );
-				httpTransport.put.mockRestore();
+				expect( superagentTransport.put ).toHaveBeenCalledWith( query, data );
+				superagentTransport.put.mockRestore();
 			} );
 
 			it( 'for DELETE requests', () => {
-				jest.spyOn( httpTransport, 'delete' ).mockImplementation( () => {} );
+				jest.spyOn( superagentTransport, 'delete' ).mockImplementation( () => {} );
 				const site = new WPAPI( {
 					endpoint: 'http://some.url.com/wp-json',
 				} );
@@ -71,8 +71,8 @@ describe( 'WPAPI', () => {
 					force: true,
 				};
 				query.delete( data );
-				expect( httpTransport.delete ).toHaveBeenCalledWith( query, data, undefined );
-				httpTransport.delete.mockRestore();
+				expect( superagentTransport.delete ).toHaveBeenCalledWith( query, data );
+				superagentTransport.delete.mockRestore();
 			} );
 
 		} );
@@ -108,11 +108,11 @@ describe( 'WPAPI', () => {
 	describe( '.discover() constructor method', () => {
 
 		beforeEach( () => {
-			jest.spyOn( httpTransport, 'get' ).mockImplementation( () => {} );
+			jest.spyOn( superagentTransport, 'get' ).mockImplementation( () => {} );
 		} );
 
 		afterEach( () => {
-			httpTransport.get.mockRestore();
+			superagentTransport.get.mockRestore();
 		} );
 
 		it( 'is a function', () => {
@@ -122,7 +122,7 @@ describe( 'WPAPI', () => {
 
 		it( 'discovers the API root with a GET request', () => {
 			const url = 'http://mozarts.house';
-			httpTransport.get.mockImplementation( () => Promise.resolve( {
+			superagentTransport.get.mockImplementation( () => Promise.resolve( {
 				name: 'Skip Beats',
 				descrition: 'Just another WordPress weblog',
 				routes: {
@@ -140,8 +140,8 @@ describe( 'WPAPI', () => {
 				.then( ( result ) => {
 					expect( result ).toBeInstanceOf( WPAPI );
 					expect( result.root().toString() ).toBe( 'http://mozarts.house/wp-json/' );
-					expect( httpTransport.get ).toBeCalledTimes( 1 );
-					const indexRequestObject = httpTransport.get.mock.calls[0][0];
+					expect( superagentTransport.get ).toBeCalledTimes( 1 );
+					const indexRequestObject = superagentTransport.get.mock.calls[0][0];
 					expect( indexRequestObject.toString() ).toBe( 'http://mozarts.house/?rest_route=%2F' );
 					return SUCCESS;
 				} );
@@ -150,7 +150,7 @@ describe( 'WPAPI', () => {
 
 		it( 'throws an error if no API endpoint can be discovered', () => {
 			const url = 'http://we.made.it/to/mozarts/house';
-			httpTransport.get.mockImplementationOnce( () => Promise.reject( 'Some error' ) );
+			superagentTransport.get.mockImplementationOnce( () => Promise.reject( 'Some error' ) );
 			const prom = WPAPI.discover( url )
 				.catch( ( err ) => {
 					expect( err ).toBe( 'Some error' );
diff --git a/tests/integration/custom-http-transport.js b/tests/integration/custom-http-transport.js
index 2668709..0748330 100644
--- a/tests/integration/custom-http-transport.js
+++ b/tests/integration/custom-http-transport.js
@@ -6,7 +6,7 @@ const credentials = require( '../helpers/constants' ).credentials;
 const SUCCESS = 'success';
 
 describe.each( [
-	[ 'wpapi/superagent', require( '../../superagent' ), require( '../../superagent/http-transport' ) ],
+	[ 'wpapi/superagent', require( '../../superagent' ), require( '../../superagent/superagent-transport' ) ],
 ] )( '%s: custom HTTP transport methods', ( transportName, WPAPI, httpTransport ) => {
 	let wp;
 	let id;
@@ -15,19 +15,16 @@ describe.each( [
 
 	beforeEach( () => {
 		cache = {};
-		cachingGet = jest.fn( ( wpreq, cb ) => {
+		cachingGet = jest.fn( ( wpreq ) => {
 			const result = cache[ wpreq ];
-			// If a cache hit is found, return it via the same callback/promise
-			// signature as the default transport method
+			// If a cache hit is found, return it via the same promise
+			// signature as that of the default transport method
 			if ( result ) {
-				if ( cb && typeof cb === 'function' ) {
-					cb( null, result );
-				}
 				return Promise.resolve( result );
 			}
 
 			// Delegate to default transport if no cached data was found
-			return WPAPI.transport.get( wpreq, cb ).then( ( result ) => {
+			return WPAPI.transport.get( wpreq ).then( ( result ) => {
 				cache[ wpreq ] = result;
 				return result;
 			} );
@@ -65,7 +62,7 @@ describe.each( [
 				expect( result.id ).toBe( id );
 				expect( cachingGet ).toBeCalledTimes( 1 );
 				expect( httpTransport.get ).toHaveBeenCalledTimes( 1 );
-				expect( httpTransport.get ).toHaveBeenCalledWith( query1, undefined );
+				expect( httpTransport.get ).toHaveBeenCalledWith( query1 );
 				expect( result ).toBe( cache[ 'http://wpapi.local/wp-json/wp/v2/posts/' + id ] );
 			} )
 			.then( () => {
@@ -75,7 +72,7 @@ describe.each( [
 			.then( ( result ) => {
 				expect( cachingGet ).toBeCalledTimes( 2 );
 				expect( httpTransport.get ).toHaveBeenCalledTimes( 1 );
-				expect( httpTransport.get ).toHaveBeenLastCalledWith( query1, undefined );
+				expect( httpTransport.get ).toHaveBeenLastCalledWith( query1 );
 				expect( httpTransport.get.mock.calls[0][0] ).not.toBe( query2 );
 				expect( result ).toBe( cache[ 'http://wpapi.local/wp-json/wp/v2/posts/' + id ] );
 				return SUCCESS;
@@ -98,16 +95,12 @@ describe.each( [
 			endpoint: 'http://wpapi.local/wp-json',
 			transport: {
 				// If .slug is used, auto-unwrap the returned array
-				get( wpreq, cb ) {
+				get( wpreq ) {
 					if ( ! wpreq._params.slug ) {
-						return WPAPI.transport.get.call( this, wpreq, cb );
+						return WPAPI.transport.get.call( this, wpreq );
 					}
 					return WPAPI.transport.get( wpreq ).then( ( results ) => {
-						const result = extractSlug( results );
-						if ( cb && typeof cb === 'function' ) {
-							cb( null, result );
-						}
-						return result;
+						return extractSlug( results );
 					} );
 				},
 			},
diff --git a/tests/integration/posts.js b/tests/integration/posts.js
index 4df2df5..0764b2d 100644
--- a/tests/integration/posts.js
+++ b/tests/integration/posts.js
@@ -538,22 +538,19 @@ describe.each( [
 			return expect( prom ).resolves.toBe( SUCCESS );
 		} );
 
-		it( 'cannot create (POST) without authentication (also tests callback-mode errors)', () => {
-			const prom = new Promise( ( resolve, reject ) => {
-				wp.posts().create( {
+		it( 'cannot create (POST) without authentication', () => {
+			const prom = wp.posts()
+				.create( {
 					title: 'New Post 2501',
 					content: 'Some Content',
-				}, ( err ) => {
-					if ( ! err ) {
-						reject();
-					}
+				} )
+				.catch( ( err ) => {
 					expect( err.code ).toBe( 'rest_cannot_create' );
 					expect( err.data ).toEqual( {
 						status: 401,
 					} );
-					resolve( SUCCESS );
+					return SUCCESS;
 				} );
-			} );
 			return expect( prom ).resolves.toBe( SUCCESS );
 		} );
 
@@ -631,15 +628,7 @@ describe.each( [
 				expect( post.title ).toHaveProperty( 'rendered' );
 				expect( post.title.rendered ).toBe( 'Updated Title' );
 				// Re-authenticate & delete (trash) this post
-				// Use a callback to exercise that part of the functionality
-				return new Promise( ( resolve, reject ) => {
-					authenticated.posts().id( id ).delete( ( err, data ) => {
-						if ( err ) {
-							return reject( err );
-						}
-						resolve( data );
-					} );
-				} );
+				return authenticated.posts().id( id ).delete();
 			} )
 			.then( ( response ) => {
 				expect( typeof response ).toBe( 'object' );
@@ -808,35 +797,4 @@ describe.each( [
 		return expect( prom ).resolves.toBe( SUCCESS );
 	}, 10000 );
 
-	// Callback context
-
-	it( 'can GET posts with a context-bound callback', ( done ) => {
-		class Ctor {
-			setState( state ) {
-				this.state = state;
-			}
-
-			request( cb ) {
-				const self = this;
-				wp.posts().get( ( err, data ) => {
-					expect( err ).toBeNull();
-
-					// Context is maintained
-					expect( this ).toBe( self );
-					this.setState( {
-						data: data,
-					} );
-
-					expect( this ).toHaveProperty( 'state' );
-					expect( typeof this.state ).toBe( 'object' );
-					expect( this.state ).toHaveProperty( 'data' );
-					expect( Array.isArray( this.state.data ) ).toBe( true );
-					expect( this.state.data.length ).toBe( 10 );
-					cb();
-				} );
-			}
-		}
-		( new Ctor() ).request( done );
-	} );
-
 } );
diff --git a/tests/unit/wpapi.js b/tests/unit/wpapi.js
index 5beba16..8f3a569 100644
--- a/tests/unit/wpapi.js
+++ b/tests/unit/wpapi.js
@@ -85,7 +85,7 @@ describe( 'WPAPI', () => {
 				} );
 				const query = site.root( '' );
 				query.get();
-				expect( customGet ).toHaveBeenCalledWith( query, undefined );
+				expect( customGet ).toHaveBeenCalledWith( query );
 				expect( WPAPI.transport.get ).not.toHaveBeenCalled();
 				WPAPI.transport.get.mockRestore();
 			} );
@@ -102,8 +102,8 @@ describe( 'WPAPI', () => {
 				} );
 				const query = site.root( '' );
 				query.get();
-				expect( customGet ).toHaveBeenCalledWith( query, undefined );
-				expect( WPAPI.transport.get ).toHaveBeenCalledWith( query, undefined );
+				expect( customGet ).toHaveBeenCalledWith( query );
+				expect( WPAPI.transport.get ).toHaveBeenCalledWith( query );
 				WPAPI.transport.get.mockRestore();
 			} );
 
@@ -120,10 +120,10 @@ describe( 'WPAPI', () => {
 				const query = site.root( 'a-resource' );
 				const data = {};
 				query.create( data );
-				expect( customPost ).toHaveBeenCalledWith( query, data, undefined );
+				expect( customPost ).toHaveBeenCalledWith( query, data );
 				expect( WPAPI.transport.post ).not.toHaveBeenCalled();
 				query.update( data );
-				expect( customPut ).toHaveBeenCalledWith( query, data, undefined );
+				expect( customPut ).toHaveBeenCalledWith( query, data );
 				expect( WPAPI.transport.put ).not.toHaveBeenCalled();
 				WPAPI.transport.post.mockRestore();
 				WPAPI.transport.put.mockRestore();
@@ -143,7 +143,7 @@ describe( 'WPAPI', () => {
 				expect( site ).not.toBe( site2 );
 				const query = site2.root( '' );
 				query.get();
-				expect( WPAPI.transport.get ).toHaveBeenCalledWith( query, undefined );
+				expect( WPAPI.transport.get ).toHaveBeenCalledWith( query );
 				expect( customGet ).not.toHaveBeenCalled();
 				WPAPI.transport.get.mockRestore();
 			} );
@@ -336,10 +336,9 @@ describe( 'WPAPI', () => {
 				site.transport( {
 					get: customGet,
 				} );
-				const cb = () => {};
 				const query = site.root( '' );
-				query.get( cb );
-				expect( customGet ).toHaveBeenCalledWith( query, cb );
+				query.get();
+				expect( customGet ).toHaveBeenCalledWith( query );
 			} );
 
 			it( 'does not impact or overwrite unspecified transport methods', () => {