Introducción a la API web de Microsoft Dynamics 365 (JavaScript del lado cliente)

 

Publicado: enero de 2017

Se aplica a: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online

En recursos web HTML, los scripts de formularios, o los comandos de la cinta de opciones, puede usar JavaScript para realizar operaciones con datos Microsoft Dynamics 365 utilizando la Web API introducida con Microsoft Dynamics 365 (en línea y local).

La Web API es especialmente fácil de usar con JavaScript y recursos web puesto que los datos JSON que se envían y reciben con ella se convierten fácilmente en objetos JavaScript. Sin embargo, la mayoría de los programadores desearán crear o usar una biblioteca JavaScript auxiliar para beneficiarse de la reutilización de código y conservar su código de la lógica de negocios independiente del código para tener acceso a los datos. Este tema describe cómo usar el objeto XMLHttpRequest para realizar operaciones con JavaScript así como oportunidades para crear bibliotecas JavaScript reutilizables que proporcionan funciones para trabajar con Web API.

En este tema

Dónde puede usar JavaScript del lado cliente

Descripción de XMLHttpRequest

Uso de XMLHttpRequest

Crear datos JSON para enviar

Análisis de JSON devuelto

Crear una función reutilizable utilizando devoluciones de llamada

Crear una función reutilizable utilizando promesas

Dónde puede usar JavaScript del lado cliente

Hay dos áreas en las que puede usar JavaScript del lado cliente para tener acceso a Microsoft Dynamics 365 mediante la API web:

  • Recursos web de JavaScript
    El código de JavaScript incluido en un recursos web de JavaScript que se ejecuta en el contexto de un recurso web HTML, scripts de formularios o comandos de la cinta de opciones.

    Al usar recursos web de JavaScript en Microsoft Dynamics 365, no necesita autenticar porque los recursos web forman parte de la aplicación en la que ya está autenticado el usuario. El resto de este tema se centrará en este escenario.Más información:Recursos web para Microsoft Dynamics 365,Recursos web de script (JScript), Uso de JavaScript con Microsoft Dynamics 365 y Bibliotecas de JavaScript para Microsoft Dynamics 365.

  • Aplicaciones de una sola página
    Código de JavaScript en una biblioteca de JavaScript de otra aplicación que se ejecuta en un explorador y que se autentica en Microsoft Dynamics 365 mediante el uso compartido de recursos de origen cruzado (CORS). Este patrón se suele utilizar para aplicaciones de una sola página.

    Al usar JavaScript en una aplicación de una sola página (SPA), puede usar la biblioteca adal.js para permitir al usuario autenticar y obtener acceso a los datos de Microsoft Dynamics 365 en una página hospedada en otro dominio. La mayor parte de la información de este tema se aplica a este escenario pero también debe integrar un encabezado de autorización en cualquier solicitud que contenga un token de autenticación. Para obtener más información, consulte: Use OAuth con Uso compartido de recursos de origen cruzado para conectar una Aplicación de una sola página a Microsoft Dynamics 365

Descripción de XMLHttpRequest

Al usar la Web API, usará un objeto XMLHttpRequest.XMLHttpRequest (XHR) es un objeto nativo encontrado en todos los exploradores modernos, y permite que las técnicas AJAX convertir la páginas web en dinámicas. Aunque el nombre del objeto contiene "XML", todas las solicitudes que usan la API web usarán JSON en lugar de XML.

XMLHttpRequest usado por marcos JavaScript

Los marcos JavaScript como jQuery suelen envolver el objeto XMLHttpRequest subyacente en una función (como $.ajax) porque anteriormente no todos los exploradores proporcionaban una XMLHttpRequest nativa de un modo estándar y también para simplificar su uso. Ahora que los exploradores modernos tienen una implementación XMLHttpRequest estándar, no necesita una biblioteca aparte para mitigar estas diferencias. Sin embargo, muchos programadores continúan usando marcos JavaScript para solicitar recursos del servidor. Aunque está bien usar jQuery y otros marcos JavaScript en SPA o recursos web HTML, se recomienda evitarlos en scripts de formularios o comandos de la cinta de opciones. Con varias soluciones que se pueden instalar en una organización, cada una incluyendo potencialmente diferentes versiones de un marco JavaScript, particularmente jQuery, pueden producirse resultados inesperados a menos que todo el mundo realice pasos para evitar conflictos. Si realiza las solicitudes de la API web en scripts de formularios o comandos de la cinta de opciones, se recomienda usar la XMLHttpRequest directamente y no tomar una dependencia en jQuery.Más información:Uso de jQuery

Este tema describe cómo usar XMLHttpRequest nativa directamente, aunque estos mismos conceptos se aplicarán al usar jQuery u otros marcos JavaScript que se ejecuten en un explorador, dado que todos ellos usan XMLHttpRequest. Puede usar una biblioteca que use XHR directamente en un explorador con cualquier marco JavaScript.

Uso de XMLHttpRequest

El siguiente es un ejemplo muy sencillo que muestra cómo crear una entidad de cuenta con la API web y el objeto XMLHttpRequest. En este ejemplo, solo la variable clientURL no está definida.

var req = new XMLHttpRequest()
req.open("POST",encodeURI(clientURL + "/api/data/v8.1/accounts"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
 if (this.readyState == 4 /* complete */) {
  req.onreadystatechange = null;
  if (this.status == 204) {
   var accountUri = this.getResponseHeader("OData-EntityId");
   console.log("Created account with URI: "+ accountUri)
  }
  else {
   var error = JSON.parse(this.response).error;
   console.log(error.message);
  }
 }
};
req.send(JSON.stringify({ name: "Sample account" }));

En las siguientes secciones se describe lo que hace este código.

Abra la XMLHttpRequest

Después de inicializar el objeto XMLHttpRequest, tiene que abrirlo para poder establecer propiedades o enviarlo. Los parámetros del método open son un método de solicitud HTTP, una URL, y un parámetro boolean para indicar si la operación debe realizarse asincrónicamente. Debe elegir siempre realizar operaciones asincrónicamente.Más información:Usar métodos de acceso a datos asincrónicos

En este ejemplo, ya que estamos creando una entidad de cuenta, debemos establecer la dirección URL para que coincida con la ruta del conjunto de entidades para el account EntityType. La URL completa de este ejemplo es clientURL + "/api/data/v8.1/accounts y la variable clientURL se debe establecer en la dirección URL raíz de la aplicación Microsoft Dynamics 365. Para recursos web con acceso al objeto de contexto, la función getClientUrl a la que se puede obtener acceso a través del objeto de contexto del lado del cliente disponible mediante la Función GetGlobalContext de un recurso web HTML o el objeto Xrm.Page.context en un script de formulario o un comando de la cinta de opciones. Debe usar la función encodeURI en cualquier URL para enviar al servicio para asegurarse de que no incluye caracteres no seguros.

Dado que esta característica crea una entidad, el método de solicitud HTTP es POST como se describe en Cree una entidad usando API web.

El método XMLHttpRequestopen también prevé especificar un nombre de usuario y una contraseña. No necesita especificar un valor para estos parámetros con recursos web porque el usuario ya está autenticado. Para las SPA, la autenticación se administra mediante un token en lugar de estos parámetros.

Establezca los encabezados y el controlador de eventos

Después de abrir la XMLHttpRequest puede aplicar un número de encabezados de solicitud con el método setRequestHeader. Normalmente debe usar los encabezados que se muestran aquí con algunas variaciones para las clases especiales de operaciones.Más información:Encabezados de HTTP.

Antes de enviar la solicitud, es necesario incluir un controlador de eventos que detecta cuando se completa la operación. Después de enviar la solicitud, progresa a través de diversos estados antes de que se devuelva la respuesta. Para capturar el momento en que XMLHttpRequest se completa, debe establecer un controlador de eventos a la propiedad onreadystatechange para detectar cuando la propiedad readystate es igual a 4, lo que indica completado. En ese momento puede examinar la propiedad status.

Nota

Una vez XMLHttpRequest se ha completado, se recomienda establecer la propiedad onreadystatechange como null para evitar problemas potenciales de la pérdida de memoria.

En la función anónima que es el controlador de eventos, después de haber comprobado la finalización, puede examinar la propiedad status para determinar si la operación ha sido correcta. En este caso, el valor esperado de estado es 204 No Content porque no se espera nada en el cuerpo de la respuesta desde una operación de creación. El URI para la cuenta creada está en el encabezado OData-EntityId y se puede acceder a él mediante el método getResponseHeader.

Si ésta fuera una operación distinta de la que se esperara que se devolvieran datos en la respuesta, tendría un valor 200 OKstatus y la función usaría JSON.parse en la respuesta XMLHttpRequest para convertir la respuesta JSON a un objeto JavaScript al que el código podría tener acceso.Más información:Análisis de JSON devuelto

Si el estado no es el valor esperado, es un error y se devuelve un objeto de error con las propiedades descritas en Errores de análisis de la respuesta. Este ejemplo se usa JSON.parse para convertir la propiedad XMLHttpRequestresponse en un objeto JavaScript para que pueda accederse a la propiedad message.

Enviar la XMLHttpRequest

Finalmente, use el método XMLHttpRequestsend para enviar la solicitud, incluidos los datos JSON necesarios. Use JSON.stringify para convertir los objetos JavaScript a cadenas JSON que se pueden incluir en el cuerpo de la solicitud cuando lo envía.

Crear datos JSON para enviar

En el ejemplo anterior, se crea la entidad de cuenta usando un único conjunto de propiedades. Para determinar qué propiedades están disponibles para una entidad debe consultar el Documento de metadatos CSDL, documentación generada a partir de ese documento, o código generado utilizando ese documento. Para las entidades empresariales del sistema incluidas en todas las organizaciones de Microsoft Dynamics 365, puede consultar la Web API EntityType Reference. Los nombres de propiedad van en minúscula y aceptan tipos de datos sencillos que correspondan a los siguientes tipos JavaScript: Boolean, Number, String, Array, Object y Date.

Nota

La única excepción al uso de los tipos de datos simples es el BooleanManagedProperty ComplexType que se usa para las entidades que almacenan datos compatibles con la solución, como recursos web, plantillas, informes, roles, consultas guardadas y entidades de metadatos. Esta propiedad nunca se usa para las entidades que almacenan datos profesionales. Las entidades de metadatos usan varios tipos complejos y siguen diferentes reglas. Para obtener más información, vea Usar la API web con metadatos de Dynamics 365.

Crear datos para enviar en una solicitud suele ser una simple cuestión de crear un objeto JavaScript ordinario y establecer las propiedades correspondientes. El siguiente código muestra dos métodos válidos para definir un objeto JavaScript con propiedades y valores. Este ejemplo usa propiedades seleccionadas de la entidad de contacto definida en contact EntityType.

var contact = new Object();
contact.firstname = "John";
contact.lastname = "Smith";
contact.accountrolecode = 2; //Employee
contact.creditonhold = false; //Number value works here too. 0 is false and 1 is true
contact.birthdate = new Date(1980, 11, 2);
contact["parentcustomerid_account@odata.bind"] = "/accounts(f3a11f36-cd9b-47c1-8c44-e65b961257ed)"

var contact = {
 firstname: "John",
 lastname: "Smith",
 accountrolecode: 2,//Employee
 creditonhold: false,
 birthdate: new Date(1980, 11, 2),
 "parentcustomerid_account@odata.bind": "/accounts(f3a11f36-cd9b-47c1-8c44-e65b961257ed)"
};

Independientemente cómo se definan estos objetos, una vez que use JSON.stringify ambos se convierten en la misma cadena JSON.

    {
     "firstname": "John",
     "lastname": "Smith",
     "accountrolecode": 2,
     "creditonhold": false,
     "birthdate": "1980-12-02T08:00:00.000Z",
     "parentcustomerid_account@odata.bind": "/accounts(f3a11f36-cd9b-47c1-8c44-e65b961257ed)"
    }

Hay ocasiones en las que debe definir una propiedad que no sigue las directrices de nomenclatura de propiedades ordinarias para JavaScript. Por ejemplo, cuando establece el valor de una propiedad de desplazamiento de un solo valor cuando crea una entidad necesita anexar @odata.bind al nombre de la propiedad y establecer el valor a una dirección URL correspondiente a la entidad relacionada. En este caso, debe definir la propiedad en un estilo de notación entre llaves como se muestra en el ejemplo anterior.

Excepto cuando trabaja con entidades de metadatos, no establecerá propiedades de entidad en un objeto. Con entidades de metadatos a menudo necesitar establecer propiedades que son valores de enumeración o de tipo complejo. Pero esto no es común con entidades de negocio ordinarias.

Cuando se crean entidades relacionadas puede establecer el valor de una propiedad navegación valorada como colección utilizando una Array, pero esta es una operación bastante especializada.Más información:Crear entidades relacionadas en una operación

Propiedades de tipo entidad

Al publicar una entidad en una acción donde el tipo de parámetro representa un tipo base para la entidad, como crmbaseentity EntityType o activitypointer EntityType, puede ser necesario incluir la propiedad @odata.type con el nombre completo del tipo de entidad como valor. Por ejemplo, dado que letter EntityType se hereda de activitypointer, quizás deba indicar explícitamente el tipo de entidad usando la siguiente propiedad y valor:"@odata.type": "Microsoft.Dynamics.CRM.letter".

Enviar los datos para operaciones de actualización

Cuando actualice entidades, es importante establecer sólo los valores de propiedad para las propiedades que piensa actualizar. No debería recuperar una entidad, actualizar propiedades de la instancia recuperada y luego usar esa instancia en una operación de actualización. En su lugar, debe crear un nuevo objeto y establecer nuevas propiedades solo para aquellas propiedades que piensa actualizar.

Si copia simplemente las propiedades de una entidad recuperada y la actualiza mediante PATCH, cada una de las propiedades que envía se considerará una actualización, incluso si el valor es el mismo que el valor actual. Si tiene habilitada la auditoría para la entidad y el atributo, indicará que los datos han cambiado cuando no hubo cambios reales en el valor.Más información:Actualización básica

Análisis de JSON devuelto

Aunque la operación de creación que se usa en el ejemplo anterior no devuelve datos JSON, la mayoría de las operaciones que utilizan GET devolverán JSON. Para la mayoría de los tipos de datos devueltos, convertir JSON en JavaScript se puede conseguir mediante la siguiente línea de código.

var data = JSON.parse(this.response)

Sin embargo, los datos que incluyen fechas son un problema porque las fechas se pasan como cadena, por ejemplo, 2015-10-25T17:23:55Z. Para convertir esto en un objeto JavaScriptDate debe usar el parámetro reviver para la función JSON.parse. El siguiente es un ejemplo de una función que se puede usar para analizar fechas.

function dateReviver(key, value) {
  var a;
  if (typeof value === 'string') {
   a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
   if (a) {
    return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
   }
  }
  return value;
 };

Para aplicar esta característica solo inclúyala como parámetro, como se indica a continuación.

var data = JSON.parse(this.response,dateReviver)

Crear una función reutilizable utilizando devoluciones de llamada

Si tiene código para realizar una operación específica le convendrá reusarlo en lugar de escribir el mismo código una y otra vez. El siguiente paso es crear una biblioteca JavaScript que contiene una función para realizar la operación con cualquier opción disponible. En este caso solo hay dos variables para la operación de creación: el nombre del conjunto de entidades y la definición JSON de la entidad para crear. En lugar de escribir todo el código indicado anteriormente, se puede contener la misma operación en una función que solo necesita unas pocas líneas de código.

Las operaciones asincrónicas con JavaScript han empleado tradicionalmente funciones de devolución de llamada como forma de capturar cualquier valor de devolución de la operación asincrónica y de continuar la lógica del programa. Con el código para la operación de creación descrita anteriormente, el objetivo aquí es permitir realizar la misma operación usando sólo el siguiente código.

MyNameSpace.WebAPI.create("accounts",
{ name: "Sample account" },
function (accountUri) { console.log("Created account with URI: " + accountUri) },
function (error) { console.log(error.message); });

En este ejemplo, MyNameSpace.WebAPI representa la recomendación de proporcionar un nombre único para cualquier función que use.Más información:Definir los nombres únicos para las funciones de JavaScript

Para esta biblioteca planeamos incluir funciones para operaciones adicionales por lo que hay una oportunidad para tener funciones privadas reutilizables en apoyo de las operaciones. El siguiente código muestra una biblioteca que demuestra esto e incluye una función MyNameSpace.WebAPI.create utilizando devoluciones de llamada.

"use strict";
var MyNameSpace = window.MyNameSpace || {};
MyNameSpace.WebAPI = MyNameSpace.WebAPI || {};
(function () {
 this.create = function (entitySetName, entity, successCallback, errorCallback) {
  var req = new XMLHttpRequest();
  req.open("POST", encodeURI(getWebAPIPath() + entitySetName), true);
  req.setRequestHeader("Accept", "application/json");
  req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
  req.setRequestHeader("OData-MaxVersion", "4.0");
  req.setRequestHeader("OData-Version", "4.0");
  req.onreadystatechange = function () {
   if (this.readyState == 4 /* complete */) {
    req.onreadystatechange = null;
    if (this.status == 204) {
     if (successCallback)
      successCallback(this.getResponseHeader("OData-EntityId"));
    }
    else {
     if (errorCallback)
      errorCallback(MyNameSpace.WebAPI.errorHandler(this.response));
    }
   }
  };
  req.send(JSON.stringify(entity));
 };

 //Internal supporting functions
 function getClientUrl() {
  //Get the organization URL
  if (typeof GetGlobalContext == "function" &&
      typeof GetGlobalContext().getClientUrl == "function") {
   return GetGlobalContext().getClientUrl();
  }
  else {
   //If GetGlobalContext is not defined check for Xrm.Page.context;
   if (typeof Xrm != "undefined" &&
       typeof Xrm.Page != "undefined" &&
       typeof Xrm.Page.context != "undefined" &&
       typeof Xrm.Page.context.getClientUrl == "function") {
    try {
     return Xrm.Page.context.getClientUrl();
    } catch (e) {
     throw new Error("Xrm.Page.context.getClientUrl is not available.");
    }
   }
   else { throw new Error("Context is not available."); }
  }
 }
 function getWebAPIPath() {
  return getClientUrl() + "/api/data/v8.1/";
 }

 // This function is called when an error callback parses the JSON response
 // It is a public function because the error callback occurs within the onreadystatechange 
 // event handler and an internal function would not be in scope.
 this.errorHandler = function (resp) {
  try {
   return JSON.parse(resp).error;
  } catch (e) {
   return new Error("Unexpected Error")
  }
 }

}).call(MyNameSpace.WebAPI);

Esta biblioteca demuestra la recomendación de definir una función en una función anónima de autoejecución (también se conoce como función anónima autoinvocada o función anónima invocada inmediatamente) y asociar la función al espacio de nombres MyNameSpace.WebAPI. Esto permite definir funciones internas a las que puede accederse con otro código. Cualquier función que se define como parte de this será pública y las funciones dentro de la función anónima puede ser usadas por funciones públicas pero no por código externo a la función anónima. El código dentro de la función no puede ser modificado por otro código en la página.

Se define el espacio de nombres de forma que no sobrescriba ningún otro código que use el mismo espacio de nombres, pero sobrescribirá cualquier función con el mismo nombre que forme parte de ese espacio de nombres. Puede crear bibliotecas independientes que agreguen funciones públicas adicionales al espacio de nombres siempre que no tengan el mismo nombre.

La función MyNameSpace.WebAPI.create proporciona los siguientes parámetros:

Nombre

Descripción

entitySetName

El nombre del conjunto de entidades para el tipo de entidad que desee crear.

entity

Un objeto con las propiedades de la entidad que desee crear.

successCallback

La función para llamar cuando se crea la entidad. El Uri de la entidad creada se pasa a esta función.

errorCallback

La característica para llamar cuando hay un error. El error se pasará a esta función.

El código que configura el objeto XMLHttpRequest se ha modificado para usar estos valores de parámetros y también una función auxiliar interna adicional getWebAPIPath que buscará el URI de la organización base y anexará la dirección URL para que coincida con el URI raíz para la API web de modo que no necesite incluirlo. El URI para la entidad creada se pasa a la successCallback si está definida. De forma similar, la función errorHandler pública se usa para analizar cualquier error que se devuelva. La función errorHandler debe ser pública porque se llamada dentro del controlador de eventos para el evento onreadystatechange y éste no está en el ámbito del espacio de nombres. Se debe llamar con el nombre completo: MyNameSpace.WebAPI.errorHandler.

Crear una función reutilizable utilizando promesas

Si bien las devoluciones de llamada se han utilizado tradicionalmente para operaciones asincrónicas, muchos programadores creen que son poco manejables, y difíciles de leer y depurar porque una serie de operaciones asincrónicas se basarán unas en otras para crear código que forma una "pirámide catastrófica" ya que la sangría hace que el código, usando funciones anónimas, continúe cada vez más allá a la derecha de la página. Aunque este problema se puede resolver con funciones con nombre en lugar de funciones anónimas, muchos programadores aprecian los beneficios que ofrecen las promesas. Un objeto Promise representa una operación que no se haya completado aún, pero que se espera completar en el futuro.

Hay muchas bibliotecas de terceros y marcos JavaScript que proporcionan distintas implementaciones de promesas.JQuery ha proporcionado un comportamiento basado en diseño CommonJS Promises/A a través de objeto Deferred y otros insisten en la compatibilidad con la especificación Promises/A+. Una explicación de las diferencias entre estas implementaciones se encuentra más allá del ámbito de este tema. El objetivo de esta sección es simplemente indicar cómo una función auxiliar para la API web de Microsoft Dynamics 365 utilizando un objeto XMLHttpRequest nativo se puede escribir para usar el objeto Promise nativo que se implementa en la mayoría de los exploradores modernos compatibles con Microsoft Dynamics 365. Los siguientes exploradores tienen una implementación nativa de promesas: Google Chrome 32, Opera 19, Mozilla Firefox 29, Apple Safari 8 y Microsoft Edge.

Nota

Internet Explorer 11 no implementa promesas nativas. Para exploradores que no implementan promesas nativas, debe incluir una biblioteca independiente para proporcionar un polyfill. Un polyfill es código que proporciona capacidades no ofrecidas de manera nativa por un explorador. Hay varios polyfills o bibliotecas que permitirán a Internet Explorer 11 tener promesas: es6-promise, q.js y bluebird.

La ventaja de usar promesas se demuestra mejor con un ejemplo. El siguiente código usa la versión de devolución de llamada de MyNameSpace.WebAPI.create para crear una cuenta y después tres tareas asociadas a ésta.

MyNameSpace.WebAPI.create("accounts",
 { name: "Sample account" },
 function (accountUri) {
  console.log("Created account with URI: " + accountUri);
  MyNameSpace.WebAPI.create("tasks",
   { subject: "Task 1", "regardingobjectid_account_task@odata.bind": accountUri },
   function () {
    MyNameSpace.WebAPI.create("tasks",
     { subject: "Task 2", "regardingobjectid_account_task@odata.bind": accountUri },
     function () {
      MyNameSpace.WebAPI.create("tasks",
       { subject: "Task 3", "regardingobjectid_account_task@odata.bind": accountUri },
       function () {
        //Finished creating three tasks
        console.log("Three tasks created");
       },
      function (error) { console.log(error.message); });
     },
     function (error) { console.log(error.message); });
   },
  function (error) { console.log(error.message); });
 },
function (error) { console.log(error.message); });

A efectos de este ejemplo, ignore el hecho de que todos estos registros se pueden crear en una sola operación mediante inserción profunda.Más información:Crear entidades relacionadas en una operación

El código de devolución de llamada es difícil porque termina en medio del bloque de código. Mientras tanto, con promesas puede crear los mismos registros con el siguiente código.

var accountUri;
MyNameSpace.WebAPI.create("accounts", { name: "Sample account" })
.then(function (aUri) {
 accountUri = aUri;
 console.log("Created account with URI: " + accountUri);
})
.then(function () {
 return MyNameSpace.WebAPI.create("tasks", { subject: "Task 1", "regardingobjectid_account_task@odata.bind": accountUri });
})
.then(function () {
 return MyNameSpace.WebAPI.create("tasks", { subject: "Task 2", "regardingobjectid_account_task@odata.bind": accountUri });
})
.then(function () {
 return MyNameSpace.WebAPI.create("tasks", { subject: "Task 3", "regardingobjectid_account_task@odata.bind": accountUri });
})
.catch(function (error) { console.log(error.message); });

El uso de promesas preserva el flujo del código y permite detectar errores que se producen en una sola función de captura.

Convertir la función con devoluciones de llamada para usar promesas es cuestión de quitar los parámetros de devolución de llamada y de devolver una XMLHttpRequest ligeramente modificada, tal como se muestra en el siguiente ejemplo de código.

return new Promise(function (resolve, reject) {
 var req = new XMLHttpRequest();
 req.open("POST", encodeURI(getWebAPIPath() + entitySetName), true);
 req.setRequestHeader("Accept", "application/json");
 req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 req.setRequestHeader("OData-MaxVersion", "4.0");
 req.setRequestHeader("OData-Version", "4.0");
 req.onreadystatechange = function () {
 if (this.readyState == 4 /* complete */) {
  req.onreadystatechange = null;
  if (this.status == 204) {
  resolve(req.getResponseHeader("OData-EntityId"));
  }
  else {
  reject(MyNameSpace.WebAPI.errorHandler(req.response));
  }
 }
 };
 req.send(JSON.stringify(entity));
});

Además de eliminar los parámetros de devolución de llamada, la XMLHttpRequest se incluye en la Promise y en lugar de pasar resultados o errores a las devoluciones de llamadas de éxito o error, se pasan a parámetros resolve o reject. El siguiente código representa la biblioteca JavaScript completa que contiene la función MyNameSpace.WebAPI.create. Lo único que queda por hacer es agregar más operaciones Web API reutilizables siguiente el mismo patrón.

"use strict";
var MyNameSpace = window.MyNameSpace || {};
MyNameSpace.WebAPI = MyNameSpace.WebAPI || {};
(function () {
 /** @description Create a new entity
  * @param {string} entitySetName The name of the entity set for the type of entity you want to create.
  * @param {object} entity An object with the properties for the entity you want to create.
  */
 this.create = function (entitySetName, entity) {
  /// <summary>Create a new entity</summary>
  /// <param name="entitySetName" type="String">The name of the entity set for the entity you want to create.</param>
  /// <param name="entity" type="Object">An object with the properties for the entity you want to create.</param>       
  if (!isString(entitySetName)) {
   throw new Error("MyNameSpace.WebAPI.create entitySetName parameter must be a string.");
  }
  if (isNullOrUndefined(entity)) {
   throw new Error("MyNameSpace.WebAPI.create entity parameter must not be null or undefined.");
  }

  return new Promise(function (resolve, reject) {
   var req = new XMLHttpRequest();
   req.open("POST", encodeURI(getWebAPIPath() + entitySetName), true);
   req.setRequestHeader("Accept", "application/json");
   req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
   req.setRequestHeader("OData-MaxVersion", "4.0");
   req.setRequestHeader("OData-Version", "4.0");
   req.onreadystatechange = function () {
    if (this.readyState == 4 /* complete */) {
     req.onreadystatechange = null;
     if (this.status == 204) {
      resolve(req.getResponseHeader("OData-EntityId"));
     }
     else {
      reject(MyNameSpace.WebAPI.errorHandler(req.response));
     }
    }
   };
   req.send(JSON.stringify(entity));
  });

 };

 //Internal supporting functions
 function getClientUrl() {
  //Get the organization URL
  if (typeof GetGlobalContext == "function" &&
      typeof GetGlobalContext().getClientUrl == "function") {
   return GetGlobalContext().getClientUrl();
  }
  else {
   //If GetGlobalContext is not defined check for Xrm.Page.context;
   if (typeof Xrm != "undefined" &&
       typeof Xrm.Page != "undefined" &&
       typeof Xrm.Page.context != "undefined" &&
       typeof Xrm.Page.context.getClientUrl == "function") {
    try {
     return Xrm.Page.context.getClientUrl();
    } catch (e) {
     throw new Error("Xrm.Page.context.getClientUrl is not available.");
    }
   }
   else { throw new Error("Context is not available."); }
  }
 }
 function getWebAPIPath() {
  return getClientUrl() + "/api/data/v8.1/";
 }

 //Internal validation functions
 function isString(obj) {
  if (typeof obj === "string") {
   return true;
  }
  return false;

 }
 function isNull(obj) {
  if (obj === null)
  { return true; }
  return false;
 }
 function isUndefined(obj) {
  if (typeof obj === "undefined") {
   return true;
  }
  return false;
 }
 function isFunction(obj) {
  if (typeof obj === "function") {
   return true;
  }
  return false;
 }
 function isNullOrUndefined(obj) {
  if (isNull(obj) || isUndefined(obj)) {
   return true;
  }
  return false;
 }
 function isFunctionOrNull(obj) {
  if (isNull(obj))
  { return true; }
  if (isFunction(obj))
  { return true; }
  return false;
 }

 // This function is called when an error callback parses the JSON response.
 // It is a public function because the error callback occurs in the onreadystatechange 
 // event handler and an internal function wouldn’t be in scope.
 this.errorHandler = function (resp) {
  try {
   return JSON.parse(resp).error;
  } catch (e) {
   return new Error("Unexpected Error")
  }
 }

}).call(MyNameSpace.WebAPI);

Ver también

Use la API web de Microsoft Dynamics 365
Trabajar con datos de Dynamics 365 utilizando recursos web
Realizar operaciones mediante la API web
Ejemplos de la API web (JavaScript del lado del cliente)
Use OAuth con Uso compartido de recursos de origen cruzado para conectar una Aplicación de una sola página a Microsoft Dynamics 365

Microsoft Dynamics 365

© 2017 Microsoft. Todos los derechos reservados. Copyright