-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Packages: Replace the apiRequest module with api-fetch module #7566
Conversation
30247fa
to
ea90c0d
Compare
717c018
to
19cd0db
Compare
packages/fetch/src/index.js
Outdated
middlewares.push( middleware ); | ||
} | ||
|
||
function request( options ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it called request
? Does this map to fetch
somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just an internal name to avoid confusion with window.fetch
(which can also be accessed by fetch
since it's a global) used on the same file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should have been called apifetch
then. Or wpfetch
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really supportive of this change—moving away from jQuery is 💯.
There's a few places where we're treating response.json()
as a promise which will cause errors. For example, the latest posts block doesn't load when I check out this branch. We're also referencing responseJSON
in quite a few error handlers which isn't right. (I haven't tested this, though.)
As a developer that's familiar with fetch
, I find it a little confusing that @wordpress/fetch
doesn't behave exactly the same as window.fetch
unless you pass it parse: false
. Maybe renaming it to apiFetch
would help with this? Alternatively, we could move the "magic" that we do (JSON parsing and response status error handling) into a function that works on regular Response objects.
return { | ||
body, | ||
headers: getResponseHeaders( xhr ), | ||
body: response.json(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
response.json()
returns a promise. We'll need to wait for it to resolve here.
.catch( ( response ) => { | ||
const failResponse = { | ||
error: true, | ||
errorMsg: response.json().message || __( 'Unknown error' ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
response.json()
returns a promise which means message
here will be undefined.
editor/store/effects.js
Outdated
@@ -302,7 +302,7 @@ export default { | |||
const { postId } = action; | |||
const basePath = wp.api.getPostTypeRoute( getCurrentPostType( getState() ) ); | |||
dispatch( removeNotice( TRASH_POST_NOTICE_ID ) ); | |||
apiRequest( { path: `/wp/v2/${ basePath }/${ postId }`, method: 'DELETE' } ).then( | |||
fetch( { path: `/wp/v2/${ basePath }/${ postId }`, method: 'DELETE' } ).then( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the error handler for this request (line 317) we reference err.responseJSON
which will now be undefined. We do this in most places that used apiRequest
.
I guess I like the auto parsing to avoid repeating the same thing over and over again in all of our implementations. I think parsing is the default in 99% of use-cases, isn't it? |
If it won't behave like fetch I think putting I definitely think we should be parsing by default; it's totally the standard use-case and it's handy to not have to worry about it! 👍 |
We can't reuse Edit Actually, we can't change this because of the way we handle endpoint/namespace support. An option would be to drop this middleware as well though. Thinking about this more, I think we should keep the single argument signature because it's better for middleware support. |
The API is fine to me, but I think using |
I'm convinced :) updating. |
editor/store/effects.js
Outdated
}; | ||
return response.json() | ||
.then( getErrorFromBody ) | ||
.catch( () => getErrorFromBody() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be more useful to have this utility function within the apiFetch package itself?
packages/api-fetch/README.md
Outdated
} ); | ||
``` | ||
|
||
The `api-fetch` package provides built-in middlewares you can use to provide a `nonce` and a custom `rootURL`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the preloading
and namespace-endpoint
middlewares be mentioned here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, how does one leverage said nonce and rootURL middlewares ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we do about https://www.npmjs.com/package/@wordpress/api-request ?
.then( ( response ) => { | ||
return response.json().then( ( body ) => ( { | ||
body: body, | ||
headers: response.headers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the shape of response.headers
here ? Is it an object implementing the Headers
interface ? That doesn't seem compatible with how this was previously shaped as a set of tuples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless I'm missing something, I don't see it being used or exposed anywhere. I think it should be fine to remove entirely.
method: 'POST', | ||
data: { name: termName }, | ||
} ).catch( ( response ) => | ||
response.json().then( ( body ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in .then
we receive a JSON payload by default ? But .catch
we receive response
always ? And in .then
we have the option to receive response
by passing parse: false
? Feels like an inconsistent developer experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, what's the alternative you suggest? Try to parse the response in errors as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That, or just abandon the auto-parse convenience. Consistency could have the bigger benefit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally prefer to keep the auto-parsing because of the duplication it can create. I'm fine with consistency between then/catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think parsing error response bodies makes sense since this package is specifically designed to work with the WP REST API, which we know returns nicely formatted error responses:
{
"code": "rest_forbidden",
"data": {
"status": 401
},
"message": "Sorry, you are not allowed to do that."
}
Most error handling can then be as simple as:
apiFetch( ... ).catch( ( { message } ) => {
console.error( message );
} );
If we cannot parse the error response body (e.g. because of a connection issue), then we should throw a similarly shaped object, e.g.:
{
code: "unknown_error",
message: "An unknown error occurred.",
}
packages/api-fetch/README.md
Outdated
} ); | ||
``` | ||
|
||
The `api-fetch` package provides built-in middlewares you can use to provide a `nonce` and a custom `rootURL`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, how does one leverage said nonce and rootURL middlewares ?
packages/api-fetch/src/index.js
Outdated
namespaceEndpointMiddleware, | ||
httpV1Middleware, | ||
raw, | ||
].reverse(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is inherited, but why do we go through the effort to Array#reverse
if we could already know what the result will be in assigning this array? i.e.
const steps = [
raw,
httpV1Middleware,
namespaceEndpointMiddleware,
...middlewares,
];
d2b1f58
to
e7bff50
Compare
packages/api-fetch/src/index.js
Outdated
.then( parseResponse ) | ||
.catch( ( response ) => { | ||
if ( ! parse ) { | ||
Promise.reject( response ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're missing a return
here.
8783df9
to
0fd300e
Compare
This should be ready to land if I can get another review :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the helper functions used in the apiFetch
promise chain should be defined outside the function and I think there are some little cleanup things that need to happen here.
reject( xhr ); | ||
} ); | ||
const basePath = wp.api.getTaxonomyRoute( this.props.slug ); | ||
// Tries to create a term or fetch it if it already exists |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wild nitpick but comments should end in a period.
this.addRequest = apiFetch( { | ||
path: `/wp/v2/${ basePath }?${ stringify( { ...DEFAULT_QUERY, search: termName } ) }`, | ||
} ); | ||
return this.addRequest.then( ( searchResult ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess since we're returning from inside the catch
call it's tough to chain this nicer but returning a promise's chained return value is a bit hard to parse/read/grok. Not sure how to make it nicer without async/await
syntax which is a whole refactor though 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find that using async/await
is not necessarily better when we use .catch
because you end up with await
inside try/catch and you also have to embed try/catch
inside try/catch
sometimes.
} ).catch( ( error ) => { | ||
const errorCode = error.code; | ||
if ( errorCode === 'term_exists' ) { | ||
// search the new category created since last fetch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a full sentence 😄
editor/store/effects.js
Outdated
request | ||
.then( | ||
( newPost ) => { | ||
const reset = isAutosave ? resetAutosave : resetPost; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty aggressive whitespace, any chance we could tighten it up a bit?
type: 'SAVE_SHARED_BLOCK_FAILURE', | ||
id: 123, | ||
} ); | ||
done(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I missed it in a previous review, but why the move to using done
here over returning the Promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was not possible on the initial implementation (I guess because of jQuery), I'll see if it's better now.
headers, | ||
} | ||
); | ||
const checkStatus = ( response ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems odd to be that these are all defined as inline functions inside the function itself. Won't that mean they're instantiated every time the function is called? Why not have these all exist outside the function itself? They're already stateless/functional and it would improve readability of the function (you only have to follow the logic of the function over the definitions as well) and it might improve performance by avoiding re-creating them on each call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseResponse is not stateless, and I think creating inline function is not bad in terms of performance so I preferred consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh shoot, I missed that.
That makes sense; I find inline functions make a function a lot tougher to understand right away, but that's cool.
@@ -32,15 +27,12 @@ const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => { | |||
.join( '&' ); | |||
} | |||
|
|||
if ( typeof options.path === 'string' ) { | |||
if ( typeof options.path === 'string' && options.parse !== false ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Does not equal false" is harder for my brain (and probably other brains) to grok than "is true". Any reason not to just use options.parse
or options.parse === true
if you want to be strict about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that it's clearer this way that it's opt-out and not opt-in. The default value allows us to change the condition but I think it's a little bit safer to check against false values on "opt-out" flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, you need to pass parse: false
to opt-out, that makes sense. But why not make the condition check if ( typeof options.path === 'string' && options.parse ) {
?
I just find it easier to read that we're checking for true rather than for "not false".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 updating
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting that if options.parse
is undefined options.parse
returns false but in the current condition it returns true
so we have to assign a default value before simplifying the check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, sorry, I should have said that; it was implied but I should've mentioned it explicitly. My bad 😞
I think that'll make the code easier to follow and the intent clearer as well. 👍
@@ -20,9 +20,8 @@ describe( 'Preloading Middleware', () => { | |||
}; | |||
|
|||
const response = prelooadingMiddleware( requestOptions ); | |||
response.done( ( value ) => { | |||
return response.then( ( value ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here you moved to returning the Promise, so I'm not sure why you went away from it before 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! I left a note about options.parse
just because I think the conditional is weird and I'm still not huge on inline functions for readability. I'd be curious about the performance difference for such a frequently-used function. 🤔
Anyway, looks good!
headers, | ||
} | ||
); | ||
const checkStatus = ( response ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh shoot, I missed that.
That makes sense; I find inline functions make a function a lot tougher to understand right away, but that's cool.
@@ -32,15 +27,12 @@ const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => { | |||
.join( '&' ); | |||
} | |||
|
|||
if ( typeof options.path === 'string' ) { | |||
if ( typeof options.path === 'string' && options.parse !== false ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, you need to pass parse: false
to opt-out, that makes sense. But why not make the condition check if ( typeof options.path === 'string' && options.parse ) {
?
I just find it easier to read that we're checking for true rather than for "not false".
1e9b4e8
to
6b96317
Compare
Thanks all for the reviews. |
We should probably just add a "deprecated" notice manually to the npm README |
@ntwb any hints, how we should proceed? |
Running diff --git a/docs/manifest.json b/docs/manifest.json
index 3efafeb88..66ae6255e 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -222,9 +222,9 @@
"parent": "packages"
},
{
- "title": "@wordpress/api-request",
- "slug": "packages-api-request",
- "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/packages/api-request/README.md",
+ "title": "@wordpress/api-fetch",
+ "slug": "packages-api-fetch",
+ "markdown_source": "https://github.com/raw/WordPress/gutenberg/master/packages/api-fetch/README.md",
"parent": "packages"
},
{ |
good catch @aduth forgot this one :) |
I've just deprecated If someone installs it they will see a notice:
|
cc @gziolo @youknowriad ☝️ |
Awesome, thanks! |
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: https://meta.svn.wordpress.org/sites/trunk/wordcamp.org/public_html/wp-content/mu-plugins@7476 74240141-8908-4e6f-9713-ba540dce6ec7
@@ -0,0 +1,64 @@ | |||
# @wordpress/api-fetch | |||
|
|||
Wrapper around `window.fetch` to call WordPress REST APIs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Should we maybe be more explicit for general use that this requires Fetch
(and thus, a polyfill maybe required for older browsers?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, definitely, this makes me think we may need a consistent way to gather the required polyfills for each package. Better automated but It's probably not easy to automate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related: #7159
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: https://meta.svn.wordpress.org/sites/trunk/wordcamp.org@7476 74240141-8908-4e6f-9713-ba540dce6ec7
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: https://meta.svn.wordpress.org/sites/trunk/wordcamp.org@7476 74240141-8908-4e6f-9713-ba540dce6ec7 git-svn-id: https://github.com/WordPress/wordcamp.org.git@1266 a64dc54a-4538-25e2-d9ce-ce57b7db7bff
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: http://meta.svn.wordpress.org/sites/trunk/wordcamp.org@7476 74240141-8908-4e6f-9713-ba540dce6ec7
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: https://meta.svn.wordpress.org/sites/trunk/wordcamp.org@7476 74240141-8908-4e6f-9713-ba540dce6ec7
Starting in Gutenberg 3.1, the 'wp-api-request' script gets de-registered and re-registered with a Gutenberg-specific version. Unfortunately, the script doesn't also get re-localized with the `wpApiSettings` variable that's needed for wp-api.js, unless the current screen contains an editor instance. The bug was reported here: WordPress/gutenberg#7488 And it should be fixed once the changes in this pull request get released: WordPress/gutenberg#7566 In the meantime, this hotfix should ensure that `wpApiSettings` is available regardless of the presence of an editor instance. git-svn-id: https://meta.svn.wordpress.org/sites/trunk/wordcamp.org@7476 74240141-8908-4e6f-9713-ba540dce6ec7
This PR follows the core-js meeting and replaces the wp.apiRequest with a new wp.fetch module not relying on jQuery APIs but using
window.fetch
Differences:
parse: false
body
directlyRemaining tasks
closes #7488 #7455 #7515