You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make the plugin (and gateway) API almost entirely async (#5295)
Before this PR, some plugin methods were sync, some were async, and some
returned ValueOrPromise (ie, could be sync or async). This led to a lack
of functionality for the never-async methods, confusing inconsistency,
and generally vagueness as to what types things are.
It's 2021: making a function async is as simple as adding the word
`async` and maybe wrapping a return type in `Promise<>`. This PR
simplifies matters by making almost every plugin and gateway method
always async.
The one exception is `willResolveField`, which is called far more than
any other plugin method. We already know that schema instrumentation has
unfortunate performance overhead so adding to it by adding more Promises
to every field doesn't seem like a good idea for now. We reserve the
write to change `willResolveField` to a `PromiseOrValue` sometimes-async
method later.
(Note that in practice, we usually either `await` or call `Promise.all`
on the return values from plugins rather than directly calling `.then`,
so if you're not using TypeScript you can probably get away with writing
sync methods; and if you are using TypeScript the compiler will quickly
show you where you're missing your `async`s.)
Specifically, this PR:
- Makes the following formerly-`ValueOrPromise` methods into async
methods: `serverWillStart`, `serverWillStop`, `didResolveSource`,
`didResolveOperation`, `didEncounterErrors`, `responseForOperation`,
`willSendResponse`
- Makes the following formerly-sync methods into async methods:
`requestDidStart`, `renderLandingPage`, `parsingDidStart` and its stop
hook, `validationDidStart` and its stop hook, `executionDidStart`,
`executionDidEnd`
- `executionWillStart` can no longer longer return an end hook as a
function; `executionWillEnd` must be returned in an object (which was
one of two options before)
- Changes the methods on `Dispatcher` so that there's only one "sync"
method (for `willResolveField`) and the other methods aren't explicitly
named with `Async`
- Simplifies the `GraphQLService` interface (used for `@apollo/gateway`)
to require the `stop` method to exist, to require `executor` to be
async, and to note that we always pass `apollo` to `load`. (All recent
versions of `@apollo/gateway` satisfy this.) Also require the
`GraphQLExecutor` function type to be async.
This also avoids the use of `ValueOrPromise<void>`, a somewhat confusing
type that means "either a Promise or a return value we shouldn't look
at" though we do still have `Promise<X|void>` types.
Note that we allow `options` and `context` functions to still have
`ValueOrPromise` semantics.
Fixes#4103. Fixes#4999. Incorporates work by @lucasconstantino from
#4050 and #4051.
Copy file name to clipboardExpand all lines: docs/source/api/plugin/usage-reporting.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,7 +113,7 @@ The only properties of the reported error you can modify are its `message` and i
113
113
114
114
Specify this asynchronous function to configure which requests are included in usage reports sent to Apollo Studio. For example, you can omit requests that execute a particular operation or requests that include a particular HTTP header.
115
115
116
-
This function is called for each received request. It takes a [`GraphQLRequestContext`](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-types/src/index.ts#L115-L150) object and must return a `Promise<Boolean>` that indicates whether to include the request. It's called either after the operation is successfully resolved (via [the `didResolveOperation` event](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#didresolveoperation)), or after it generates an error (via [the `didEncounterErrors` event](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#didencountererrors)).
116
+
This function is called for each received request. It takes a [`GraphQLRequestContext`](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-types/src/index.ts#L115-L150) object and must return a `Promise<Boolean>` that indicates whether to include the request. It's called either after the operation is successfully resolved (via [the `didResolveOperation` event](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#didresolveoperation)), or when sending the final error response if the operation was not successfully resolved (via [the `willSendResponse` event](https://www.apollographql.com/docs/apollo-server/integrations/plugins/#willsendresponse)).
117
117
118
118
By default, all requests are included in usage reports.
Copy file name to clipboardExpand all lines: docs/source/integrations/plugins.md
+57-53Lines changed: 57 additions & 53 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,7 +19,7 @@ events. Here's a basic plugin that responds to the `serverWillStart` event:
19
19
20
20
```js:title=index.js
21
21
constmyPlugin= {
22
-
serverWillStart() {
22
+
asyncserverWillStart() {
23
23
console.log('Server starting up!');
24
24
},
25
25
};
@@ -32,7 +32,7 @@ you can export it as a separate module:
32
32
33
33
```js:title=myplugin.js
34
34
module.exports= {
35
-
serverWillStart() {
35
+
asyncserverWillStart() {
36
36
console.log('Server starting up!');
37
37
},
38
38
};
@@ -44,7 +44,7 @@ To create a plugin that accepts options, create a function that accepts an
44
44
```js:title=myplugin.js
45
45
module.exports= (options) => {
46
46
return {
47
-
serverWillStart() {
47
+
asyncserverWillStart() {
48
48
console.log(options.logMessage);
49
49
},
50
50
};
@@ -57,7 +57,9 @@ module.exports = (options) => {
57
57
A plugin specifies exactly which [events](#apollo-server-event-reference)
58
58
it responds to by implementing functions that correspond to those events.
59
59
The plugin in the examples above responds to the `serverWillStart` event, which
60
-
fires when Apollo Server is preparing to start up.
60
+
fires when Apollo Server is preparing to start up. Almost all plugin events
61
+
are `async` functions (ie, functions that return `Promise`s); the one exception
62
+
is [`willResolveField`](#willresolvefield).
61
63
62
64
A plugin can respond to any combination of supported events.
63
65
@@ -79,7 +81,7 @@ lifecycle:
79
81
80
82
```js
81
83
constmyPlugin= {
82
-
requestDidStart() {
84
+
asyncrequestDidStart() {
83
85
console.log('Request started!');
84
86
},
85
87
};
@@ -92,19 +94,17 @@ just like you respond to `serverWillStart`, but you _also_ use this function
92
94
93
95
```js
94
96
constmyPlugin= {
95
-
requestDidStart(requestContext) {
97
+
asyn crequestDidStart(requestContext) {
96
98
console.log('Request started!');
97
99
98
100
return {
99
-
100
-
parsingDidStart(requestContext) {
101
+
asyncparsingDidStart(requestContext) {
101
102
console.log('Parsing started!');
102
103
},
103
104
104
-
validationDidStart(requestContext) {
105
+
asyncvalidationDidStart(requestContext) {
105
106
console.log('Validation started!');
106
107
}
107
-
108
108
}
109
109
},
110
110
};
@@ -150,42 +150,47 @@ that is invoked after the corresponding lifecycle phase _ends_:
150
150
151
151
*[`parsingDidStart`](#parsingdidstart)
152
152
*[`validationDidStart`](#validationdidstart)
153
-
*[`executionDidStart`](#executiondidstart) (this handler can alternatively return an _object_ containing an `executionDidEnd` function)
154
153
*[`willResolveField`](#willresolvefield)
155
154
155
+
([`executionDidStart`](#executiondidstart) returns an _object_ containing an `executionDidEnd` function instead of just a function as an end handler; that's because the returned object can also contain `willResolveField`.)
156
+
157
+
Just like the event handers themselves, these end hooks are async functions (except for the end hook for `willResolveField`).
158
+
156
159
These **end hooks** are passed any errors that occurred during the
157
160
execution of that lifecycle phase. For example, the following plugin logs
158
161
any errors that occur during any of the above lifecycle events:
159
162
160
163
```js
161
164
constmyPlugin= {
162
-
requestDidStart() {
165
+
asyncrequestDidStart() {
163
166
return {
164
-
parsingDidStart() {
165
-
return (err) => {
167
+
asyncparsingDidStart() {
168
+
returnasync(err) => {
166
169
if (err) {
167
170
console.error(err);
168
171
}
169
172
}
170
173
},
171
-
validationDidStart() {
174
+
asyncvalidationDidStart() {
172
175
// This end hook is unique in that it can receive an array of errors,
173
176
// which will contain every validation error that occurred.
174
-
return (errs) => {
177
+
returnasync(errs) => {
175
178
if (errs) {
176
179
errs.forEach(err=>console.error(err));
177
180
}
178
181
}
179
182
},
180
-
executionDidStart() {
181
-
return (err) => {
182
-
if (err) {
183
-
console.error(err);
183
+
asyncexecutionDidStart() {
184
+
return {
185
+
asyncexecutionDidEnd(err) {
186
+
if (err) {
187
+
console.error(err);
188
+
}
184
189
}
185
-
}
186
-
}
187
-
}
188
-
}
190
+
};
191
+
},
192
+
};
193
+
},
189
194
}
190
195
```
191
196
@@ -230,7 +235,7 @@ const server = new ApolloServer({
230
235
231
236
/* This plugin is defined in-line. */
232
237
{
233
-
serverWillStart() {
238
+
asyncserverWillStart() {
234
239
console.log('Server starting up!');
235
240
},
236
241
}
@@ -252,8 +257,7 @@ Request lifecycle events are associated with a specific request. You define resp
252
257
253
258
### `serverWillStart`
254
259
255
-
The `serverWillStart` event fires when Apollo Server is preparing to start serving GraphQL requests. If you respond to this event with an `async` function (or if the function returns a `Promise`), the server doesn't start until the asynchronous operation completes. If the `Promise` is _rejected_, startup _fails_ (**unless you're using [Express middleware](/integrations/middleware/)**). This helps you make sure all
256
-
of your server's dependencies are available before attempting to begin serving requests.
260
+
The `serverWillStart` event fires when Apollo Server is preparing to start serving GraphQL requests. The server doesn't start until this asynchronous method completes. If it throws (ie, if the `Promise` it returns is _rejected_), startup _fails_ and your server will not serve GraphQL operations. This helps you make sure all of your server's dependencies are available before attempting to begin serving requests. (Specifically, this is fired from the `listen()` method in `apollo-server`, from the `start()` method for a framework integration like `apollo-server-express`, and during the first request for a serverless integration like `apollo-server-lambda`.)
257
261
258
262
#### Example
259
263
@@ -263,7 +267,7 @@ const server = new ApolloServer({
263
267
264
268
plugins: [
265
269
{
266
-
serverWillStart() {
270
+
asyncserverWillStart() {
267
271
console.log('Server starting!');
268
272
}
269
273
}
@@ -285,10 +289,10 @@ const server = new ApolloServer({
`executionDidStart` may return an ["end hook"](#end-hooks) function. Alternatively, it may return an object with one or both of the methods `executionDidEnd` and `willResolveField`. `executionDidEnd` is treated identically to an end hook: it is called after execution with any errors that occurred. `willResolveField` is documented in the next section.
482
+
`executionDidStart` may return an object with one or both of the methods `executionDidEnd` and `willResolveField`. `executionDidEnd` is treated like an end hook: it is called after execution with any errors that occurred. `willResolveField` is documented in the next section. (In Apollo Server 2, `executionDidStart` could return also return an end hook directly.)
481
483
482
484
### `willResolveField`
483
485
@@ -487,6 +489,8 @@ You provide your `willResolveField` handler in the object returned by your [`exe
487
489
488
490
Your `willResolveField` handler can optionally return an ["end hook"](#end-hooks) function that's invoked with the resolver's result (or the error that it throws). The end hook is called when your resolver has _fully_ resolved (e.g., if the resolver returns a Promise, the hook is called with the Promise's eventual resolved result).
489
491
492
+
`willResolveField` and its end hook are the only synchronous plugin APIs (ie, they do not return `Promise`s).
493
+
490
494
#### Example
491
495
492
496
```js
@@ -495,9 +499,9 @@ const server = new ApolloServer({
0 commit comments