Skip to content

Commit 8c9a2bc

Browse files
authored
Breaking: Remove support for "Node-style" error-first callbacks (#428)
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. * Refactor superagent transport: extract bind-transport method * Remove error-first callback support from superagent transport Maintaining this node-style interface, which support threads indicate is rarely used (especially given the rising popularity of `async`/`await`), no longer outweights the benefit of providing a consistent interface regardless of the transport library used. * Update WPRequest & WPAPI for promise-only method signatures This completes the work to phase out the "node-style" error-first callbacks * Update CHANGELOG with breaking transport method signature change * Add note on callback support removal in v2 upgrade guide
1 parent 613655c commit 8c9a2bc

File tree

12 files changed

+130
-203
lines changed

12 files changed

+130
-203
lines changed

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Changelog
22

33
## v2.0.0 [**alpha**] _Second Toughest in the Infants_
4-
4+
- **BREAKING**: "Node-style" error-first callbacks (_e.g._ `.get( ( err, data ) => {} )`) are no longer supported. All transport request methods return Promises.
55
- **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.
66
- **BREAKING**: Autodiscovery now either succeeds or fails; a WPAPI instance configured with default routes will no longer be returned.
77

@@ -11,13 +11,11 @@
1111
- Throw an error early when `.file()` is passed a Buffer object without an accompanying name string, props @cungminh2710 & @mvhirsch
1212

1313

14-
1514
## v1.2.1 _Colomb_
1615

1716
- Fix issue where `li` was improperly declared as a dev-only dependency, props @el-lsan & @jerolan.
1817

1918

20-
2119
## v1.2.0 _Space Is Only Noise_
2220

2321
- **BREAKING**: The minimum supported node version is now v8.6, or v8.2.1 with the `--harmony` flag.

README.md

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ At present this browser bundle tracks the `wpapi/superagent` module, and include
7878

7979
### Upgrading from v1
8080

81+
### Require `wpapi/superagent`
82+
8183
Prior to version 2.0 (currently `alpha` status) this library shipped with built-in HTTP functionality using Superagent.
8284

8385
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
9395
+++ const WPAPI = require( 'wpapi/superagent' );
9496
```
9597

98+
### Use Promises instead of Callbacks
99+
100+
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.
101+
102+
```js
103+
// Version 1
104+
wp.posts().get( function( error, posts ) { /* ... */ } );
105+
106+
// Version 2, Promises syntax
107+
wp.posts().get().then( posts => { /* ... */ } );
108+
109+
// Version 2, await syntax
110+
await wp.posts().get();
111+
```
112+
96113
## Using the Client
97114

98115
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' });
103120
```
104121
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!)
105122

106-
We support requesting posts using either a callback-style or promise-style syntax:
123+
**Compatibility Note:** As of Version 2.0, Node-style error-first callbacks are no longer supported by this library. All request methods return Promises.
107124

108125
```javascript
109-
// Callbacks
110-
wp.posts().get(function( err, data ) {
111-
if ( err ) {
112-
// handle err
113-
}
114-
// do something with the returned posts
115-
});
116-
117-
// Promises
118-
wp.posts().then(function( data ) {
119-
// do something with the returned posts
120-
}).catch(function( err ) {
121-
// handle error
122-
});
126+
// Request methods return Promises.
127+
wp.posts().get()
128+
.then(function( data ) {
129+
// do something with the returned posts
130+
})
131+
.catch(function( err ) {
132+
// handle error
133+
});
123134
```
124135
The `wp` object has endpoint handler methods for every endpoint that ships with the default WordPress REST API plugin.
125136

@@ -798,7 +809,9 @@ By default `node-wpapi` uses the [superagent](https://www.npmjs.com/package/supe
798809

799810
**This is advanced behavior; you will only need to utilize this functionality if your application has very specific HTTP handling or caching requirements.**
800811

801-
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).
812+
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).
813+
814+
**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.
802815

803816
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.
804817

@@ -809,16 +822,11 @@ var site = new WPAPI({
809822
endpoint: 'http://my-site.com/wp-json',
810823
transport: {
811824
// Only override the transport for the GET method, in this example
812-
// Transport methods should take a wpreq object and a callback:
813-
get: function( wpreq, cb ) {
825+
// Transport methods should take a wpreq object:
826+
get: function( wpreq ) {
814827
var result = cache[ wpreq ];
815-
// If a cache hit is found, return it via the same callback/promise
816-
// signature as the default transport method:
828+
// If a cache hit is found, return it wrapped in a Promise:
817829
if ( result ) {
818-
if ( cb && typeof cb === 'function' ) {
819-
// Invoke the callback function, if one was provided
820-
cb( null, result );
821-
}
822830
// Return the data as a promise
823831
return Promise.resolve( result );
824832
}
@@ -837,8 +845,8 @@ You may set one or many custom HTTP transport methods on an existing WP site cli
837845

838846
```js
839847
site.transport({
840-
get: function( wpreq, callbackFn ) { /* ... */},
841-
put: function( wpreq, callbackFn ) { /* ... */}
848+
get: function( wpreq ) { /* ... */},
849+
put: function( wpreq, data ) { /* ... */}
842850
});
843851
```
844852

lib/bind-transport.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Utility method for binding a frozen transport object to the WPAPI constructor
3+
*
4+
* See /axios and /superagent directories
5+
* @param {Function} WPAPI The WPAPI constructor
6+
* @param {Object} httpTransport The HTTP transport object
7+
* @returns {Function} The WPAPI object augmented with the provided transport
8+
*/
9+
module.exports = function( WPAPI, httpTransport ) {
10+
WPAPI.transport = Object.create( httpTransport );
11+
Object.freeze( WPAPI.transport );
12+
return WPAPI;
13+
};

lib/constructors/wp-request.js

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -709,23 +709,21 @@ WPRequest.prototype.setHeaders = function( headers, value ) {
709709
*
710710
* @method
711711
* @async
712-
* @param {Function} [callback] A callback to invoke with the results of the GET request
713712
* @returns {Promise} A promise to the results of the HTTP request
714713
*/
715-
WPRequest.prototype.get = function( callback ) {
716-
return this.transport.get( this, callback );
714+
WPRequest.prototype.get = function() {
715+
return this.transport.get( this );
717716
};
718717

719718
/**
720719
* Get the headers for the specified resource
721720
*
722721
* @method
723722
* @async
724-
* @param {Function} [callback] A callback to invoke with the results of the HEAD request
725723
* @returns {Promise} A promise to the header results of the HTTP request
726724
*/
727-
WPRequest.prototype.headers = function( callback ) {
728-
return this.transport.head( this, callback );
725+
WPRequest.prototype.headers = function() {
726+
return this.transport.head( this );
729727
};
730728

731729
/**
@@ -736,11 +734,10 @@ WPRequest.prototype.headers = function( callback ) {
736734
* @method
737735
* @async
738736
* @param {Object} data The data for the POST request
739-
* @param {Function} [callback] A callback to invoke with the results of the POST request
740737
* @returns {Promise} A promise to the results of the HTTP request
741738
*/
742-
WPRequest.prototype.create = function( data, callback ) {
743-
return this.transport.post( this, data, callback );
739+
WPRequest.prototype.create = function( data ) {
740+
return this.transport.post( this, data );
744741
};
745742

746743
/**
@@ -752,11 +749,10 @@ WPRequest.prototype.create = function( data, callback ) {
752749
* @async
753750
* @private
754751
* @param {Object} data The data for the PUT request
755-
* @param {Function} [callback] A callback to invoke with the results of the PUT request
756752
* @returns {Promise} A promise to the results of the HTTP request
757753
*/
758-
WPRequest.prototype.update = function( data, callback ) {
759-
return this.transport.put( this, data, callback );
754+
WPRequest.prototype.update = function( data ) {
755+
return this.transport.put( this, data );
760756
};
761757

762758
/**
@@ -765,11 +761,10 @@ WPRequest.prototype.update = function( data, callback ) {
765761
* @method
766762
* @async
767763
* @param {Object} [data] Data to send along with the DELETE request
768-
* @param {Function} [callback] A callback to invoke with the results of the DELETE request
769764
* @returns {Promise} A promise to the results of the HTTP request
770765
*/
771-
WPRequest.prototype.delete = function( data, callback ) {
772-
return this.transport.delete( this, data, callback );
766+
WPRequest.prototype.delete = function( data ) {
767+
return this.transport.delete( this, data );
773768
};
774769

775770
/**

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"**/tests/**/*.js"
3131
],
3232
"testPathIgnorePatterns": [
33+
"test.js",
3334
".eslintrc.js",
3435
"/tests/helpers/"
3536
],

superagent/index.js

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
11
const WPAPI = require( '../wpapi' );
2+
const superagentTransport = require( './superagent-transport' );
3+
const bindTransport = require( '../lib/bind-transport' );
24

3-
// Pull in superagent-based HTTP transport
4-
const httpTransport = require( './http-transport' );
5-
6-
/**
7-
* The HTTP transport methods object used by all WPAPI instances
8-
*
9-
* These methods may be extended or replaced on an instance-by-instance basis
10-
*
11-
* @memberof! WPAPI
12-
* @static
13-
* @property transport
14-
* @type {Object}
15-
*/
16-
WPAPI.transport = Object.create( httpTransport );
17-
Object.freeze( WPAPI.transport );
18-
19-
module.exports = WPAPI;
5+
// Bind the superagent-based HTTP transport to the WPAPI constructor
6+
module.exports = bindTransport( WPAPI, superagentTransport );

superagent/http-transport.js renamed to superagent/superagent-transport.js

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,15 @@ function createPaginationObject( result, options, httpTransport ) {
179179
// ====================
180180

181181
/**
182-
* Submit the provided superagent request object, invoke a callback (if it was
183-
* provided), and return a promise to the response from the HTTP request.
182+
* Submit the provided superagent request object and return a promise which
183+
* resolves to the response from the HTTP request.
184184
*
185185
* @private
186186
* @param {Object} request A superagent request object
187-
* @param {Function} callback A callback function (optional)
188187
* @param {Function} transform A function to transform the result data
189188
* @returns {Promise} A promise to the superagent request
190189
*/
191-
function invokeAndPromisify( request, callback, transform ) {
190+
function invokeAndPromisify( request, transform ) {
192191
return new Promise( ( resolve, reject ) => {
193192
// Fire off the result
194193
request.end( ( err, result ) => {
@@ -200,14 +199,7 @@ function invokeAndPromisify( request, callback, transform ) {
200199
resolve( result );
201200
}
202201
} );
203-
} ).then( transform ).then( ( result ) => {
204-
// If a node-style callback was provided, call it, but also return the
205-
// result value for use via the returned Promise
206-
if ( callback && typeof callback === 'function' ) {
207-
callback( null, result );
208-
}
209-
return result;
210-
}, ( err ) => {
202+
} ).then( transform ).catch( ( err ) => {
211203
// If the API provided an error object, it will be available within the
212204
// superagent response object as response.body (containing the response
213205
// JSON). If that object exists, it will have a .code property if it is
@@ -217,13 +209,8 @@ function invokeAndPromisify( request, callback, transform ) {
217209
// all transport-specific (superagent-specific) properties
218210
err = err.response.body;
219211
}
220-
// If a callback was provided, ensure it is called with the error; otherwise
221-
// re-throw the error so that it can be handled by a Promise .catch or .then
222-
if ( callback && typeof callback === 'function' ) {
223-
callback( err );
224-
} else {
225-
throw err;
226-
}
212+
// Re-throw the error so that it can be handled by a Promise .catch or .then
213+
throw err;
227214
} );
228215
}
229216

@@ -264,17 +251,16 @@ function returnHeaders( result ) {
264251
* @method get
265252
* @async
266253
* @param {WPRequest} wpreq A WPRequest query object
267-
* @param {Function} [callback] A callback to invoke with the results of the GET request
268254
* @returns {Promise} A promise to the results of the HTTP request
269255
*/
270-
function _httpGet( wpreq, callback ) {
256+
function _httpGet( wpreq ) {
271257
checkMethodSupport( 'get', wpreq );
272258
const url = wpreq.toString();
273259

274260
let request = _auth( agent.get( url ), wpreq._options );
275261
request = _setHeaders( request, wpreq._options );
276262

277-
return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
263+
return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
278264
}
279265

280266
/**
@@ -283,10 +269,9 @@ function _httpGet( wpreq, callback ) {
283269
* @async
284270
* @param {WPRequest} wpreq A WPRequest query object
285271
* @param {Object} data The data for the POST request
286-
* @param {Function} [callback] A callback to invoke with the results of the POST request
287272
* @returns {Promise} A promise to the results of the HTTP request
288273
*/
289-
function _httpPost( wpreq, data, callback ) {
274+
function _httpPost( wpreq, data ) {
290275
checkMethodSupport( 'post', wpreq );
291276
const url = wpreq.toString();
292277
data = data || {};
@@ -304,63 +289,56 @@ function _httpPost( wpreq, data, callback ) {
304289
request = request.send( data );
305290
}
306291

307-
return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
292+
return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
308293
}
309294

310295
/**
311296
* @method put
312297
* @async
313298
* @param {WPRequest} wpreq A WPRequest query object
314299
* @param {Object} data The data for the PUT request
315-
* @param {Function} [callback] A callback to invoke with the results of the PUT request
316300
* @returns {Promise} A promise to the results of the HTTP request
317301
*/
318-
function _httpPut( wpreq, data, callback ) {
302+
function _httpPut( wpreq, data ) {
319303
checkMethodSupport( 'put', wpreq );
320304
const url = wpreq.toString();
321305
data = data || {};
322306

323307
let request = _auth( agent.put( url ), wpreq._options, true ).send( data );
324308
request = _setHeaders( request, wpreq._options );
325309

326-
return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
310+
return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
327311
}
328312

329313
/**
330314
* @method delete
331315
* @async
332316
* @param {WPRequest} wpreq A WPRequest query object
333317
* @param {Object} [data] Data to send along with the DELETE request
334-
* @param {Function} [callback] A callback to invoke with the results of the DELETE request
335318
* @returns {Promise} A promise to the results of the HTTP request
336319
*/
337-
function _httpDelete( wpreq, data, callback ) {
338-
if ( ! callback && typeof data === 'function' ) {
339-
callback = data;
340-
data = null;
341-
}
320+
function _httpDelete( wpreq, data ) {
342321
checkMethodSupport( 'delete', wpreq );
343322
const url = wpreq.toString();
344323
let request = _auth( agent.del( url ), wpreq._options, true ).send( data );
345324
request = _setHeaders( request, wpreq._options );
346325

347-
return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
326+
return invokeAndPromisify( request, returnBody.bind( null, wpreq ) );
348327
}
349328

350329
/**
351330
* @method head
352331
* @async
353332
* @param {WPRequest} wpreq A WPRequest query object
354-
* @param {Function} [callback] A callback to invoke with the results of the HEAD request
355333
* @returns {Promise} A promise to the header results of the HTTP request
356334
*/
357-
function _httpHead( wpreq, callback ) {
335+
function _httpHead( wpreq ) {
358336
checkMethodSupport( 'head', wpreq );
359337
const url = wpreq.toString();
360338
let request = _auth( agent.head( url ), wpreq._options );
361339
request = _setHeaders( request, wpreq._options );
362340

363-
return invokeAndPromisify( request, callback, returnHeaders );
341+
return invokeAndPromisify( request, returnHeaders );
364342
}
365343

366344
module.exports = {

0 commit comments

Comments
 (0)