Replies: 2 comments 4 replies
-
Apollo Server's implementation does have the form you describe as bad, although the function it calls is unlikely to throw. Re 1, I think the comment I had was more about telling the difference between non-incremental and incremental responses, rather than between telling the difference between initial and subsequent results. I do think that the typing benefits are nice with the current implementation. Given that initial and subsequent results really are different types, it is helpful for type checking that they are accessed in different ways instead of being differentiated only temporally. |
Beta Was this translation helpful? Give feedback.
-
This was discussed during the July 2025 WG meeting. The consensus was:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Continuing the discussion on the spec PR for the response section and especially this comment:
First, a little history. The initial defer/stream proposals and the published v16 experimental versions returned an
ExecutionResult | AsyncIterable<ExecutionResult | ExecutionPatchResult>
, i.e. the response was either a map or a stream, where the first item in the stream is the initial execution result, and the remainder of the items are "patches." After plenty of iteration in v17, the equivalent nowadays would be something more likeExecutionResult | AsyncGenerator<InitialIncrementalExecutionResult | SubsequentIncrementalExecutionResult>
, and we might further iterate on the naming in graphql/graphql-spec#1124), but the idea is the same, the response should be a regular response or an incremental stream, where the first item differs in type from all of the rest.Since August 2022, and from version
17.0.0-alpha.1
onwards, however, the graphql-js reference implementation has returned something else, namely:`ExecutionResult | {
initialResult: InitialIncrementalExecutionResult;
subsequentResults: AsyncGenerator;
} as implemented by @glasser
Advantages of the newer incremental response map type:
in
keyword to test to see whether we have anAsyncGenerator
, but I find that to no longer be the case.)initialResult
orsubsequentResults
or evenextensions
that should be scoped to any one result.Disadvantages:
subsequentResults
stream is not strictly implemented lazily, which it may not be, for example, whenearlyExecution: true
.In particular, consider the following code:
if the stream is embedded within a map containing the one might naively write code something like this:
if
doSomethingWithSubsequentResult
throws, it does not callincrementalStream.return()
automatically. This could be fixed as follows:but if we have the initialResult be the first item in the stream, you could structure your code in the non embedded case as follows:
This snippets demonstrates the requirement to use assertions/casts (
as InitialResult
/as SubsequentResult
), a disadvantage of this apparoach, but also lets us get an automaticincrementalStream.return()
if anything in the loop throws, based onfor await (...)
semantics.Alternatively, because the
incrementalStream
is typed as aAsyncGenerator
which is anAsyncIterableIterator
, we could see some users write syntax such as the below to avoid theprocessedInitialResult
check:which would require them to add the same explicit
try { ... } catch(e: unknwon) { incrementalStream.return(); throw(e) }
bit, such that forcing the user to access the stream may not be safer after all. @benjie responded (via Discord) that mixing.next()
calls and thefor await (...)
idiom is probably not best practice (one could argue that it's an even an an anti-pattern?). Alternatively, you could argue that forcing the user to access a stream may not help in all cases (i.e. the snippet directly above, but it would help in some, i.e. the snippet with justfor await (...)
and we should do our best to nudge users into the pit of success.Finally, one last consideration:
Beta Was this translation helpful? Give feedback.
All reactions