@@ -171,10 +171,13 @@ private static function executeOperation(ExecutionContext $exeContext, Operation
171
171
172
172
$ path = [];
173
173
if ($ operation ->operation === 'mutation ' ) {
174
- return self ::executeFieldsSerially ($ exeContext , $ type , $ rootValue , $ path , $ fields );
174
+ $ results = self ::executeFieldsSerially ($ exeContext , $ type , $ rootValue , $ path , $ fields );
175
+ } else {
176
+ $ results = self ::executeFields ($ exeContext , $ type , $ rootValue , $ path , $ fields );
175
177
}
178
+ $ finalResults = self ::completePromiseIfNeeded ($ results );
176
179
177
- return self :: executeFields ( $ exeContext , $ type , $ rootValue , $ path , $ fields ) ;
180
+ return $ finalResults ;
178
181
}
179
182
180
183
@@ -223,18 +226,34 @@ private static function getOperationRootType(Schema $schema, OperationDefinition
223
226
*/
224
227
private static function executeFieldsSerially (ExecutionContext $ exeContext , ObjectType $ parentType , $ sourceValue , $ path , $ fields )
225
228
{
229
+ if (self ::isThenable ($ sourceValue )) {
230
+ return $ sourceValue ->then (function ($ resolvedSourceValue ) use ($ exeContext , $ parentType , $ path , $ fields ) {
231
+ return self ::executeFieldsSerially ($ exeContext , $ parentType , $ resolvedSourceValue , $ path , $ fields );
232
+ });
233
+ }
234
+
226
235
$ results = [];
227
- $ sourceValue = self ::completePromiseIfNeeded ($ sourceValue );
228
236
229
- foreach ( $ fields as $ responseName => $ fieldASTs ) {
237
+ $ process = function ( $ responseName , $ fieldASTs , $ results ) use ( $ path , $ exeContext , $ parentType , $ sourceValue ) {
230
238
$ fieldPath = $ path ;
231
239
$ fieldPath [] = $ responseName ;
232
240
$ result = self ::resolveField ($ exeContext , $ parentType , $ sourceValue , $ fieldASTs , $ fieldPath );
233
241
234
- if ($ result !== self ::$ UNDEFINED ) {
235
- $ result = self ::completePromiseIfNeeded ($ result );
236
- // Undefined means that field is not defined in schema
237
- $ results [$ responseName ] = $ result ;
242
+ // Undefined means that field is not defined in schema
243
+ if ($ result === self ::$ UNDEFINED ) {
244
+ return $ results ;
245
+ }
246
+ $ results [$ responseName ] = $ result ;
247
+ return $ results ;
248
+ };
249
+
250
+ foreach ($ fields as $ responseName => $ fieldASTs ) {
251
+ if (self ::isThenable ($ results )) {
252
+ $ results = $ results ->then (function ($ resolvedResults ) use ($ responseName , $ fieldASTs , $ process ) {
253
+ return $ process ($ responseName , $ fieldASTs , $ resolvedResults );
254
+ });
255
+ } else {
256
+ $ results = $ process ($ responseName , $ fieldASTs , $ results );
238
257
}
239
258
}
240
259
// see #59
@@ -250,12 +269,9 @@ private static function executeFieldsSerially(ExecutionContext $exeContext, Obje
250
269
*/
251
270
private static function executeFields (ExecutionContext $ exeContext , ObjectType $ parentType , $ source , $ path , $ fields )
252
271
{
253
- // Native PHP doesn't support promises.
254
- // Custom executor should be built for platforms like ReactPHP
255
272
return self ::executeFieldsSerially ($ exeContext , $ parentType , $ source , $ path , $ fields );
256
273
}
257
274
258
-
259
275
/**
260
276
* Given a selectionSet, adds all of the fields in that selection to
261
277
* the passed in map of fields, and returns it at the end.
@@ -454,8 +470,6 @@ private static function resolveField(ExecutionContext $exeContext, ObjectType $p
454
470
// Get the resolve function, regardless of if its result is normal
455
471
// or abrupt (error).
456
472
$ result = self ::resolveOrError ($ resolveFn , $ source , $ args , $ context , $ info );
457
- $ result = self ::completePromiseIfNeeded ($ result );
458
-
459
473
$ result = self ::completeValueCatchingError (
460
474
$ exeContext ,
461
475
$ returnType ,
@@ -516,14 +530,26 @@ private static function completeValueCatchingError(
516
530
// Otherwise, error protection is applied, logging the error and resolving
517
531
// a null value for this field if one is encountered.
518
532
try {
519
- return self ::completeValueWithLocatedError (
533
+ $ completed = self ::completeValueWithLocatedError (
520
534
$ exeContext ,
521
535
$ returnType ,
522
536
$ fieldASTs ,
523
537
$ info ,
524
538
$ path ,
525
539
$ result
526
540
);
541
+ if (self ::isThenable ($ completed )) {
542
+ // If `completeValueWithLocatedError` returned a rejected promise, log
543
+ // the rejection error and resolve to null.
544
+ // Note: we don't rely on a `catch` method, but we do expect "thenable"
545
+ // to take a second callback for the error case.
546
+ return $ completed ->then (null , function ($ err ) use ($ exeContext ) {
547
+ $ exeContext ->addError ($ err );
548
+ return null ;
549
+ });
550
+ }
551
+
552
+ return $ completed ;
527
553
} catch (Error $ err ) {
528
554
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
529
555
// and return null.
@@ -532,7 +558,6 @@ private static function completeValueCatchingError(
532
558
}
533
559
}
534
560
535
-
536
561
/**
537
562
* This is a small wrapper around completeValue which annotates errors with
538
563
* location information.
@@ -546,7 +571,7 @@ private static function completeValueCatchingError(
546
571
* @return array|null
547
572
* @throws Error
548
573
*/
549
- static function completeValueWithLocatedError (
574
+ public static function completeValueWithLocatedError (
550
575
ExecutionContext $ exeContext ,
551
576
Type $ returnType ,
552
577
$ fieldASTs ,
@@ -556,14 +581,20 @@ static function completeValueWithLocatedError(
556
581
)
557
582
{
558
583
try {
559
- return self ::completeValue (
584
+ $ completed = self ::completeValue (
560
585
$ exeContext ,
561
586
$ returnType ,
562
587
$ fieldASTs ,
563
588
$ info ,
564
589
$ path ,
565
590
$ result
566
591
);
592
+ if (self ::isThenable ($ completed )) {
593
+ return $ completed ->then (null , function ($ error ) use ($ fieldASTs , $ path ) {
594
+ throw Error::createLocatedError ($ error , $ fieldASTs , $ path );
595
+ });
596
+ }
597
+ return $ completed ;
567
598
} catch (\Exception $ error ) {
568
599
throw Error::createLocatedError ($ error , $ fieldASTs , $ path );
569
600
}
@@ -609,6 +640,20 @@ private static function completeValue(
609
640
&$ result
610
641
)
611
642
{
643
+ // If result is a Promise, apply-lift over completeValue.
644
+ if (self ::isThenable ($ result )) {
645
+ return $ result ->then (function ($ resolved ) use ($ exeContext , $ returnType , $ fieldASTs , $ info , $ path ) {
646
+ return self ::completeValue (
647
+ $ exeContext ,
648
+ $ returnType ,
649
+ $ fieldASTs ,
650
+ $ info ,
651
+ $ path ,
652
+ $ resolved
653
+ );
654
+ });
655
+ }
656
+
612
657
if ($ result instanceof \Exception) {
613
658
throw $ result ;
614
659
}
@@ -879,12 +924,24 @@ private static function inferTypeOf($value, $context, ResolveInfo $info, Abstrac
879
924
return null ;
880
925
}
881
926
882
- private static function completePromiseIfNeeded ( $ result )
927
+ private static function isThenable ( $ value )
883
928
{
884
- if ($ result instanceof PromiseInterface) {
885
- $ result = $ result ->wait ();
929
+ return ($ value instanceof PromiseInterface);
930
+ }
931
+
932
+ private static function completePromiseIfNeeded ($ value )
933
+ {
934
+ if (self ::isThenable ($ value )) {
935
+ $ results = self ::completePromiseIfNeeded ($ value ->wait ());
936
+ return $ results ;
937
+ } elseif (is_array ($ value ) || $ value instanceof \Traversable) {
938
+ $ results = [];
939
+ foreach ($ value as $ key => $ item ) {
940
+ $ results [$ key ] = self ::completePromiseIfNeeded ($ item );
941
+ }
942
+ return $ results ;
886
943
}
887
944
888
- return $ result ;
945
+ return $ value ;
889
946
}
890
947
}
0 commit comments