

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Uso de operaciones por lotes de DynamoDB en AWS AppSync
<a name="tutorial-dynamodb-batch-js"></a>

AWS AppSync admite el uso de operaciones por lotes de Amazon DynamoDB en una o más tablas de una sola región. Las operaciones admitidas son `BatchGetItem`, `BatchPutItem` y `BatchDeleteItem`. Al utilizar estas funciones en AWS AppSync, puede realizar tareas como:
+ Transferir una lista de claves en una sola consulta y devolver los resultados desde una tabla
+ Leer registros desde una o varias tablas en una única consulta
+ Escribir registros de forma masiva en una o varias tablas
+ Escribir o eliminar condicionalmente registros en varias tablas que pueden tener una relación

Las operaciones por lotes AWS AppSync tienen dos diferencias clave con respecto a las operaciones sin lotes:
+ El rol del origen de datos debe tener permisos para todas las tablas a las que el solucionador obtiene acceso.
+ La especificación de tabla de un solucionador forma parte del objeto de solicitud.

## Lotes en una única tabla
<a name="single-table-batch-js"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

Para empezar, vamos a crear una nueva API de GraphQL. En la AWS AppSync consola, selecciona **Crear API**, **GraphQL** y APIs **Diseñar desde cero**. Asigne a su API el nombre `BatchTutorial API`, elija **Siguiente** y, en el paso **Especificar recursos de GraphQL**, elija **Crear recursos de GraphQL más adelante**. Luego, haga clic en **Siguiente**. Revise sus detalles y cree la API. Ve a la página de **esquemas** y pega el siguiente esquema. Ten en cuenta que, para la consulta, pasaremos una lista de IDs:

```
type Post {
    id: ID!
    title: String
}

input PostInput {
    id: ID!
    title: String
}

type Query {
    batchGet(ids: [ID]): [Post]
}

type Mutation {
    batchAdd(posts: [PostInput]): [Post]
    batchDelete(ids: [ID]): [Post]
}
```

Guarde el esquema y elija **Crear recursos** en la parte superior de la página. Elija **Usar tipo existente** y, a continuación, elija el tipo `Post`. Llame a su tabla `Posts`. Asegúrese de que **Clave principal** está establecido en `id`, desmarque **Generar GraphQL automáticamente** (usted proporcionará su propio código) y seleccione **Crear**. Para empezar, AWS AppSync crea una nueva tabla de DynamoDB y un origen de datos conectado a la tabla con los roles adecuados. Sin embargo, todavía hay un par de permisos que debe añadir al rol. Vaya a la página **Orígenes de datos** y elija el nuevo origen de datos. En **Seleccionar un rol existente**, verá que se ha creado automáticamente un rol para la tabla. Tome nota del rol (debería tener un aspecto similar`appsync-ds-ddb-aaabbbcccddd-Posts`) y, a continuación, vaya a la consola de IAM ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)). En la consola de IAM, seleccione **Roles** y, a continuación, seleccione su rol en la tabla. En su rol, en **Políticas de permisos**, haga clic en el botón `+` situado junto a la política (debe tener un nombre similar al del rol). Seleccione **Editar** en la parte superior del menú desplegable cuando aparezca la política. Debe añadir permisos por lotes a su política, específicamente `dynamodb:BatchGetItem` y `dynamodb:BatchWriteItem`. Tendrá un aspecto similar al siguiente:

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:BatchGetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/locationReadings",
                "arn:aws:dynamodb:us-east-1:111122223333:table/locationReadings/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/temperatureReadings",
                "arn:aws:dynamodb:us-east-1:111122223333:table/temperatureReadings/*"
            ]
        }
    ]
}
```

------

Seleccione **Siguiente** y, a continuación, **Guardar cambios**. Su política debería permitir ahora el procesamiento por lotes.

De vuelta a la AWS AppSync consola, ve a la página del **esquema** y selecciona **Adjuntar** junto al `Mutation.batchAdd` campo. Cree su solucionador utilizando la tabla `Posts` como origen de datos. En el editor de código, sustituya los controladores por el siguiente fragmento. Este fragmento toma automáticamente cada elemento con el tipo de `input PostInput` de GraphQL y crea un mapa, lo que es necesario para la operación `BatchPutItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchPutItem",
    tables: {
      Posts: ctx.args.posts.map((post) => util.dynamodb.toMapValues(post)),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Vaya a la página de **consultas** de la AWS AppSync consola y ejecute la siguiente `batchAdd` mutación:

```
mutation add {
    batchAdd(posts:[{
            id: 1 title: "Running in the Park"},{
            id: 2 title: "Playing fetch"
        }]){
            id
            title
    }
}
```

Debería ver los resultados impresos en la pantalla; para validarlo, consulte la consola de DynamoDB para buscar los valores escritos en la tabla `Posts`.

A continuación, repita el proceso para asociar un solucionador, pero para el campo `Query.batchGet`, utilice la tabla `Posts` como origen de datos. Sustituya los controladores por el siguiente código. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `BatchGetItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchGetItem",
    tables: {
      Posts: {
        keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
        consistentRead: true,
      },
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Ahora, regrese a la página de **consultas** de la AWS AppSync consola y ejecute la siguiente `batchGet` consulta:

```
query get {
    batchGet(ids:[1,2,3]){
        id
        title
    }
}
```

Esto debe devolver los resultados para los dos valores `id` que ha añadido anteriormente. Observe que se devuelve un valor `null` para el `id` con el valor `3`. Esto se debe a que aún no hay ningún registro en la tabla `Posts` con ese valor. Tenga en cuenta también que AWS AppSync devuelve los resultados en el mismo orden que las claves transferidas a la consulta, que es una función adicional que AWS AppSync funciona en su nombre. Por lo tanto, si cambia a `batchGet(ids:[1,3,2])`, verá que el orden cambia. También sabrá por qué `id` devuelve un valor `null`.

Por último, asocie un solucionador más al campo `Mutation.batchDelete` utilizando la tabla `Posts` como origen de datos. Sustituya los controladores por el siguiente código. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `BatchGetItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchDeleteItem",
    tables: {
      Posts: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Ahora, vuelve a la página de **consultas** de la AWS AppSync consola y ejecuta la siguiente `batchDelete` mutación:

```
mutation delete {
    batchDelete(ids:[1,2]){ id }
}
```

Ahora se eliminarán los registros con `id` `1` y `2`. Si vuelve a ejecutar la consulta `batchGet()` anterior, devolverá `null`.

## Lotes en varias tablas
<a name="multi-table-batch-js"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

AWS AppSync también permite realizar operaciones por lotes en todas las tablas. Vamos a crear una aplicación más compleja. Imagine que queremos crear una aplicación de salud para mascotas, con sensores que comunican la ubicación y temperatura corporal de la mascota. Los sensores funcionan con pilas e intentan conectarse a la red cada pocos minutos. Cuando un sensor establece una conexión, envía sus lecturas a nuestra AWS AppSync API. A continuación, los disparadores analizan los datos para presentar un panel al propietario de la mascota. Concentrémonos en representar las interacciones entre el sensor y el almacén de datos de backend.

En la AWS AppSync consola, selecciona **Crear API**, **GraphQL** y APIs **Diseñar desde cero**. Asigne a su API el nombre `MultiBatchTutorial API`, elija **Siguiente** y, en el paso **Especificar recursos de GraphQL**, elija **Crear recursos de GraphQL más adelante**. Luego, haga clic en **Siguiente**. Revise sus detalles y cree la API. Vaya a la página **Esquema** y pegue y guarde el siguiente esquema:

```
type Mutation {
    # Register a batch of readings
    recordReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
    # Delete a batch of readings
    deleteReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
}

type Query {
    # Retrieve all possible readings recorded by a sensor at a specific time
    getReadings(sensorId: ID!, timestamp: String!): [SensorReading]
}

type RecordResult {
    temperatureReadings: [TemperatureReading]
    locationReadings: [LocationReading]
}

interface SensorReading {
    sensorId: ID!
    timestamp: String!
}

# Sensor reading representing the sensor temperature (in Fahrenheit)
type TemperatureReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    value: Float
}

# Sensor reading representing the sensor location (lat,long)
type LocationReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    lat: Float
    long: Float
}

input TemperatureReadingInput {
    sensorId: ID!
    timestamp: String
    value: Float
}

input LocationReadingInput {
    sensorId: ID!
    timestamp: String
    lat: Float
    long: Float
}
```

Necesitamos crear dos tablas de DynamoDB:
+ `locationReadings` almacenará las lecturas de ubicación de los sensores.
+ `temperatureReadings` almacenará las lecturas de temperatura de los sensores.

Ambas tablas compartirán la misma estructura de clave principal: `sensorId (String)` como clave de partición y `timestamp (String)` como clave de clasificación.

Elija **Crear recursos** en la parte superior de la página. Elija **Usar tipo existente** y, a continuación, elija el tipo `locationReadings`. Llame a su tabla `locationReadings`. Asegúrese de que **Clave principal** está configurada en `sensorId` y la clave de clasificación en`timestamp`. Desmarque **Generar GraphQL automáticamente** (proporcionará su propio código) y seleccione **Crear**. Repita este proceso para `temperatureReadings` utilizando `temperatureReadings` como tipo y nombre de la tabla. Utilice las mismas claves que antes.

Las nuevas tablas contendrán los roles generados automáticamente. Todavía hay que añadir un par de permisos a esos roles. Vaya a la página **Orígenes de datos** y elija `locationReadings`. En **Seleccione un rol existente**, puede ver el rol. Tome nota del rol (debería tener un aspecto similar`appsync-ds-ddb-aaabbbcccddd-locationReadings`) y, a continuación, vaya a la consola de IAM () [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/). En la consola de IAM, seleccione **Roles** y, a continuación, seleccione su rol en la tabla. En su rol, en **Políticas de permisos**, haga clic en el botón `+` situado junto a la política (debe tener un nombre similar al del rol). Seleccione **Editar** en la parte superior del menú desplegable cuando aparezca la política. Debe añadir permisos a esta política. Tendrá un aspecto similar al siguiente:

Seleccione **Siguiente** y, a continuación, **Guardar cambios**. Repita este proceso para la el origen de datos `temperatureReadings` utilizando el mismo fragmento de política anterior.

### BatchPutItem - Grabar las lecturas de los sensores
<a name="batchputitem-recording-sensor-readings-js"></a>

Nuestros sensores tiene que poder enviar sus lecturas cuando se conectan a Internet. El campo de GraphQL `Mutation.recordReadings` es la API que utilizarán para hacerlo. Necesitamos añadir un solucionador a este campo.

En la página de **esquemas** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Mutation.recordReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Tras crear el solucionador, sustituya los controladores por el siguiente código en el editor. Esta operación `BatchPutItem` nos permite especificar varias tablas: 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const locationReadings = locReadings.map((loc) => util.dynamodb.toMapValues(loc))
	const temperatureReadings = tempReadings.map((tmp) => util.dynamodb.toMapValues(tmp))

	return {
		operation: 'BatchPutItem',
		tables: {
			locationReadings,
			temperatureReadings,
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

Con operaciones por lotes, la invocación puede devolver tanto errores como resultados. Por lo tanto, podemos realizar algún control de errores adicional.

**nota**  
El uso de `utils.appendError()` es similar al de `util.error()`, con la gran diferencia de que no interrumpe la evaluación del controlador de solicitudes o respuestas. En su lugar, indica que hubo un error con el campo, pero permite al controlador evaluar la plantilla y, por tanto, devolver los datos al intermediario. Recomendamos que utilice `utils.appendError()` cuando la aplicación tenga que devolver resultados parciales.

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora podemos enviar algunas lecturas de los sensores.

Ejecute la mutación siguiente:

```
mutation sendReadings {
  recordReadings(
    tempReadings: [
      {sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]
    locReadings: [
      {sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

Enviamos diez lecturas de sensores en una mutación con lecturas repartidas en dos tablas. Utilice la consola de DynamoDB para validar que los datos aparecen en las tablas `locationReadings` y `temperatureReadings`.

### BatchDeleteItem - Eliminar las lecturas de los sensores
<a name="batchdeleteitem-deleting-sensor-readings-js"></a>

Del mismo modo, también es necesario poder eliminar lotes de lecturas de sensores. Vamos a utilizar el campo de GraphQL `Mutation.deleteReadings` para este fin. En la página de **esquema** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Mutation.deleteReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Tras crear el solucionador, sustituya los controladores del editor de código por el siguiente fragmento. En este solucionador, utilizamos un mapeador de funciones auxiliar que extrae `sensorId` y `timestamp` de las entradas proporcionadas. 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const mapper = ({ sensorId, timestamp }) => util.dynamodb.toMapValues({ sensorId, timestamp })

	return {
		operation: 'BatchDeleteItem',
		tables: {
			locationReadings: locReadings.map(mapper),
			temperatureReadings: tempReadings.map(mapper),
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a eliminar un par de lecturas de sensores.

Ejecute la mutación siguiente:

```
mutation deleteReadings {
  # Let's delete the first two readings we recorded
  deleteReadings(
    tempReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]
    locReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

**nota**  
Al contrario que en la operación `DeleteItem`, el elemento completamente eliminado no se devuelve en la respuesta. Solo se devuelve la clave pasada. Para obtener más información, consulte la [referencia de BatchDeleteItem la JavaScript función de resolución para DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item).

Valide a través de la consola de DynamoDB que estas dos lecturas se han eliminado de las tablas `locationReadings` y `temperatureReadings`.

### BatchGetItem - Recupera las lecturas
<a name="batchgetitem-retrieve-readings-js"></a>

Otra operación habitual en nuestra aplicación es obtener las lecturas de un sensor en un momento determinado. Vamos a asociar un solucionador al campo de GraphQL `Query.getReadings` en nuestro esquema. En la página de **esquema** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Query.getReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Vamos a utilizar el siguiente código: 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const keys = [util.dynamodb.toMapValues(ctx.args)]
	const consistentRead = true
	return {
		operation: 'BatchGetItem',
		tables: {
			locationReadings: { keys, consistentRead },
			temperatureReadings: { keys, consistentRead },
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	const { locationReadings: locs, temperatureReadings: temps } = ctx.result.data

	return [
		...locs.map((l) => ({ ...l, __typename: 'LocationReading' })),
		...temps.map((t) => ({ ...t, __typename: 'TemperatureReading' })),
	]
}
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a recuperar las lecturas de nuestro sensor.

Ejecute la siguiente consulta:

```
query getReadingsForSensorAndTime {
  # Let's retrieve the very first two readings
  getReadings(sensorId: 1, timestamp: "2018-02-01T17:21:06.000+08:00") {
    sensorId
    timestamp
    ...on TemperatureReading {
      value
    }
    ...on LocationReading {
      lat
      long
    }
  }
}
```

Hemos demostrado con éxito el uso de las operaciones por lotes de DynamoDB mediante. AWS AppSync

## Gestión de errores
<a name="error-handling-js"></a>

En AWS AppSync, las operaciones de fuentes de datos a veces pueden arrojar resultados parciales. "Resultados parciales" es el término que utilizaremos para indicar que el resultado de una operación se compone de algunos datos y un error. Como la gestión de errores es intrínsecamente específica de la aplicación, AWS AppSync le brinda la oportunidad de gestionar los errores en el controlador de respuestas. El error de invocación del solucionador, si lo hay, está disponible en el contexto como `ctx.error`. Los errores de invocación siempre incluyen un mensaje y un tipo a los que se tiene acceso como propiedades `ctx.error.message` y `ctx.error.type`. En el controlador de respuestas, puede gestionar los resultados parciales de tres maneras:

1. Pasar por alto el error de la invocación y limitarse a devolver los datos.

1. Generar un error (con `util.error(...)`) y detener la evaluación del controlador, con lo que no se devuelven datos.

1. Agregar un error (con `util.appendError(...)`) y también devolver los datos.

Vamos a demostrar cada una de estas posibilidades con operaciones por lotes de DynamoDB.

### Operaciones por lotes de DynamoDB
<a name="dynamodb-batch-operations-js"></a>

Con las operaciones por lotes de DynamoDB, es posible que un lote se complete parcialmente. Es decir, es posible que algunos de los elementos o claves solicitados se queden sin procesar. Si AWS AppSync no puede completar un lote, los elementos no procesados y un error de invocación se establecerán en el contexto.

Vamos a implementar la gestión de errores utilizando la configuración del campo `Query.getReadings` de la operación `BatchGetItem` de la sección anterior de este tutorial. Esta vez, supongamos que mientras se ejecutaba el campo `Query.getReadings`, la tabla de DynamoDB `temperatureReadings` agotó el desempeño aprovisionado. DynamoDB generó `ProvisionedThroughputExceededException` un durante el segundo intento al procesar AWS AppSync los elementos restantes del lote.

El siguiente código JSON representa el contexto serializado después de la invocación por lotes de DynamoDB, pero antes de que se llamara al controlador de respuestas:

```
{
  "arguments": {
    "sensorId": "1",
    "timestamp": "2018-02-01T17:21:05.000+08:00"
  },
  "source": null,
  "result": {
    "data": {
      "temperatureReadings": [
        null
      ],
      "locationReadings": [
        {
          "lat": 47.615063,
          "long": -122.333551,
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ]
    },
    "unprocessedKeys": {
      "temperatureReadings": [
        {
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ],
      "locationReadings": []
    }
  },
  "error": {
    "type": "DynamoDB:ProvisionedThroughputExceededException",
    "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
  },
  "outErrors": []
}
```

Cabe tener en cuenta algunos aspectos del contexto:
+ El error de invocación se estableció en el contexto en `ctx.error` by AWS AppSync y el tipo de error se estableció en. `DynamoDB:ProvisionedThroughputExceededException`
+ Los resultados se mapean para cada tabla en `ctx.result.data`, aunque haya un error.
+ Las claves que quedaron sin procesar están disponibles en `ctx.result.data.unprocessedKeys`. En este caso, no AWS AppSync se pudo recuperar el elemento con la clave (SensorID:1, TimeStamp:2018-02-01T 17:21:05.000 \$1 08:00) debido a un rendimiento insuficiente de la tabla.

**nota**  
Para `BatchPutItem`, es `ctx.result.data.unprocessedItems`. Para `BatchDeleteItem`, es `ctx.result.data.unprocessedKeys`.

Vamos a gestionar este error de tres formas diferentes.

#### 1. Pasar por alto el error de invocación
<a name="swallowing-the-invocation-error-js"></a>

Si se devuelven los datos sin gestionar el error de invocación se pasa por alto el error, lo que hace que el resultado para el campo de GraphQL indicado siempre tenga éxito.

El código que escribimos ya es conocido y solo se centra en los datos de resultados.

**Controlador de respuestas**

```
export function response(ctx) {
  return ctx.result.data
}
```

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": [
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "lat": 47.615063,
        "long": -122.333551
      },
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  }
}
```

No se añadirán errores a la respuesta, ya que solo se actúa en los datos.

#### 2. Se genera un error para abortar la ejecución del controlador de respuestas
<a name="raising-an-error-to-abort-the-response-execution-js"></a>

Cuando los errores parciales se deban tratar como errores completos desde la perspectiva del cliente, puede anular la ejecución del controlador de respuestas para evitar la devolución de datos. El método de utilidad `util.error(...)` consigue exactamente ese comportamiento.

**Código de controlador de respuestas**

```
export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": null
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

Aunque podrían haberse devuelto algunos resultados de la operación por lotes de DynamoDB, decidimos generar un error que hace que el campo de GraphQL `getReadings` sea nulo y se añada el error al bloque de *errors* de la respuesta de GraphQL.

#### 3. Añadir el error para devolver tanto los datos como los errores
<a name="appending-an-error-to-return-both-data-and-errors-js"></a>

En algunos casos, para proporcionar una mejor experiencia al usuario, las aplicaciones pueden devolver resultados parciales e informar a sus clientes de los elementos que no se han procesado. Los clientes pueden decidir probar de nuevo o trasladar el error al usuario final. El método `util.appendError(...)` es la utilidad que permite este comportamiento al permitir que el creador de la aplicación agregue errores al contexto sin interferir con la evaluación del controlador de respuestas. Tras evaluar el controlador de respuestas, AWS AppSync procesará los errores de contexto añadiéndolos al bloque de errores de la respuesta de GraphQL.

**Código de controlador de respuestas**

```
export function response(ctx) {
  if (ctx.error) {
    util.appendError(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

Hemos reenviado el error de invocación y el elemento `unprocessedKeys` dentro del bloque de errores de la respuesta de GraphQL. El campo `getReadings` también devuelve datos parciales de la tabla `locationReadings`, como puede ver en la respuesta a continuación.

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": [
      null,
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```