code: private function executeFields(ObjectType $parentType, $source, $path, $fields)
      {
          $containsPromise = false;
          $finalResults    = [];
          foreach ($fields as $responseName => $fieldNodes) {
              $fieldPath   = $path;
              $fieldPath[] = $responseName;
              $result      = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath);
              if ($result === self::$UNDEFINED) {
                  continue;
              }
              if (! $containsPromise && $this->getPromise($result)) {
                  $containsPromise = true;
              }
              $finalResults[$responseName] = $result;
          }
          // If there are no promises, we can just return the object
          if (! $containsPromise) {
              return self::fixResultsIfEmptyArray($finalResults);
          }
  
          // Otherwise, results is a map from field name to the result
          // of resolving that field, which is possibly a promise. Return
          // a promise that will return this same map, but with any
          // promises replaced with the values they resolved to.
          return $this->promiseForAssocArray($finalResults);
      }
docs: Implements the "Evaluating selection sets" section of the spec
  for "read" mode.
  
  @param mixed|null  $source
  @param mixed[]     $path
  @param ArrayObject $fields
  
  @return Promise|stdClass|mixed[]