다음을 통해 공유


Microsoft Dynamics 365 웹 API(클라이언트 쪽 JavaScript) 시작하기

 

게시 날짜: 2017년 1월

적용 대상: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online

HTML 웹 리소스, 양식 스크립트 또는 리본 명령에서는 JavaScript를 사용하여 Microsoft Dynamics 365 데이터에 대해 Microsoft Dynamics 365(온라인 및 온-프레미스)로 도입된 Web API를 사용하여 작업을 수행할 수 있습니다.

Web API는 이것으로 전송 및 수신하는 JSON 데이터가 JavaScript 개체로 쉽게 변환되기 때문에 특히 JavaScript와 웹 리소스와 함께 사용하기 쉽습니다. 하지만 대부분의 개발자는 코드 재사용의 이점을 이용하고 코드와는 별도로 데이터에 액세스하기 위한 비즈니스 논리 코드를 유지하기 위해 도우미 JavaScript 라이브러리를 만들거나 사용합니다. 이 항목에서는 XMLHttpRequest 개체를 사용하여 JavaScript로 작업을 수행하는 방법과 Web API로 작업할 수 있는 기능을 제공하는 재사용 가능한 JavaScript 라이브러리를 만들 기회에 대해 설명합니다.

이 항목의 내용

클라이언트 쪽 JavaScript를 사용할 수 있는 영역

XMLHttpRequest 이해

XMLHttpRequest 사용

보낼 JSON 데이터 작성

구문 분석 JSON 반환

콜백을 사용하여 재사용 가능한 함수 만들기

Promise를 사용하여 재사용 가능한 함수 만들기

클라이언트 쪽 JavaScript를 사용할 수 있는 영역

웹 API를 사용하여 Microsoft Dynamics 365에 액세스하기 위해 클라이언트 쪽 JavaScript를 사용할 수 있는 영역은 두 가지입니다.

  • JavaScript 웹 리소스
    HTML 웹 리소스, 폼 스크립트, 또는 리본 명령의 컨텍스트에서 실행되는 JavaScript 웹 리소스에 포함된 JavaScript 코드입니다.

    Microsoft Dynamics 365에서 JavaScript 웹 리소스를 사용하는 경우 사용자는 이미 인증된 응용 프로그램의 일부인 웹 리소스를 인증할 필요가 없습니다. 이 항목의 나머지 부분은 시나리오에 초점을 맞춥니다.추가 정보:Microsoft Dynamics 365용 웹 리소스,스크립트(JScript) 웹 리소스, Microsoft Dynamics 365에서 JavaScript 사용, 및 Microsoft Dynamics 365용 JavaScript 라이브러리.

  • 단일 페이지 응용 프로그램
    브라우저에서 실행되는 원본 간 자원 공유(CORS)를 사용하여 Microsoft Dynamics 365 에 인증하는 다른 응용 프로그램으로부터의 JavaScript 라이브러리의 JavaScript 코드입니다. 이 패턴은 단일 페이지 응용 프로그램에 주로 사용됩니다.

    단일 페이지 응용 프로그램(SPA)에서 JavaScript를 사용하는 경우 adal.js 라이브러리를 사용하여 사용자를 인증하고 다른 도메인에서 호스팅되는 페이지의 Microsoft Dynamics 365 데이터에 액세스할 수 있습니다. 이 항목의 정보 대부분은 이 시나리오에 적용되지만 인증 토큰이 포함된 모든 요청에 인증 머리말 또한 통합해야 합니다. 자세한 내용은 원본간 리소스 공유에 OAuth를 사용하여 Microsoft Dynamics 365에 단일 페이지 응용 프로그램 연결을 참조하십시오.

XMLHttpRequest 이해

Web API를 사용하는 경우 XMLHttpRequest 개체를 사용합니다.**XMLHttpRequest (XHR)**는 모든 최신 브라우저에 있는 네이티브 개체이고 웹 페이지를 동적으로 만드는 AJAX 기술을 사용합니다. 개체의 이름에 “XML”이 포함되어 있지만 웹 API를 사용하는 모든 요청은 XML이 아니라 JSON을 사용합니다.

JavaScript 프레임워크에 사용된 XMLHttpRequest

jQuery 등의 JavaScript프레임워크는 이전에는 일부 브라우저가 표준 방식으로 및 사용을 간소화하기 위해 네이티브 XMLHttpRequest를 제공하지 않았기 때문에 기능에 기본 XMLHttpRequest 개체(예: $.ajax)를 배치하는 경우가 많습니다. 최신 브라우저는 표준 XMLHttpRequest 구현되고 이러한 차이를 완화하는 데 별도의 라이브러리가 필요하지 않습니다. 하지만 많은 개발자는 계속 JavaScript 프레임워크에 의존하여 서버 리소스를 요청합니다.HTML 웹 리소스 또는 SPA에서 jQuery 및 다른 JavaScript 프레임워크를 사용해도 좋지만 양식 스크립트 또는 리본 명령에서는 피하는 것이 좋습니다. 조직에 대해 설치할 수 있는 다양한 솔루션의 경우 특히 jQuery와 같이 잠재적으로 다양한 버전의 JavaScript 프레임워크가 포함될 수 있는데 충돌을 방지하기 위해 모두가 단계를 수행하지 않는 이상 예측하지 못한 결과를 초래할 수 있습니다. 양식 스크립트 또는 리본 명령에서 웹 API 요청을 수행하는 경우 XMLHttpRequest를 직접 사용하고 jQuery에 종속되지 않는 것이 좋습니다.추가 정보:jQuery 사용

이 항목에서는 네이티브 XMLHttpRequest를 직접 사용하는 방법을 설명하지만 브라우저에서 실행되는 jQuery 또는 다른 JavaScript 프레임워크를 사용할 때 모두 XMLHttpRequest를 사용하기 때문에 같은 개념이 적용됩니다. XHR를 직접 사용하는 라이브러리를 모든 JavaScript 프레임워크와 함께 사용할 수 있습니다.

XMLHttpRequest 사용

다음은 웹 API와 XMLHttpRequest 개체를 사용하는 거래처 엔터티를 만드는 방법을 보여주는 아주 간단한 예입니다. 이 예에서는 clientURL 변수만 정의되어 있지 않습니다.

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" }));

다음 섹션에서는 이 코드가 무엇인지 설명합니다.

XMLHttpRequest 열기

XMLHttpRequest 개체를 초기화한 후 설정을 선택하거나 보내려면 열어야 합니다.open 메서드 매개 변수는 HTTP 요청 메서드이고 URL 및 boolean 매개 변수는 작업을 비동기적으로 수행해야 하는지 표시합니다. 항상 작업을 비동기적으로 수행해야 합니다.추가 정보:비동기 데이터 액세스 메서드 사용

이 예에서는 거래처 엔터티를 만들기 때문에 account EntityType에 대한 엔터티 설정 경로와 일치하는 URL을 설정해야 합니다. 이 예제의 전체 URL은 clientURL + "/api/data/v8.1/accounts이며 clientURL 변수는 Microsoft Dynamics 365 응용 프로그램의 루트 URL로 설정해야 합니다. 컨텍스트 개체에 대한 액세스할 수 있는 웹 리소스의 경우 HTML 웹 리소스의 GetGlobalContext 함수 또는 양식 스크립트나 리본 명령의 Xrm.Page.context 개체를 사용하여 클라이언트 쪽 컨텍스트 개체를 통해 평가할 수 있는 getClientUrl 기능입니다. 서비스에 보내는 모든 URL에서 안전하지 않은 문자가 포함되지 않도록 하려면 encodeURI 기능을 사용해야 합니다.

이 기능이 엔터티를 만들기 때문에 HTTP 요청 메서드는 웹 API를 사용하여 엔터티 만들기에 설명된 대로 POST입니다.

또한 XMLHttpRequestopen 메서드는 사용자 이름 및 암호 지정을 제공합니다. 사용자에게 이미 권한이 있기 때문에 웹 리소스로 이러한 매개 변수에 대해 값을 지정하지 않아도 됩니다. SPA의 경우, 매개 변수 대신 토큰을 통해 인증이 관리됩니다.

머리글 및 이벤트 처리기 설정

XMLHttpRequest를 연 후 setRequestHeader 메서드를 사용하여 많은 요청 머리글을 적용할 수 있습니다. 일반적으로 여기에 표시된 머리글을 특별한 종류의 작업의 경우 약간의 변형을 가해 사용해야 합니다.추가 정보:HTTP 헤더.

요청을 보내기 전에 작업이 완료되는 때를 감지하는 이벤트 처리기를 포함해야 합니다. 요청을 보낸 후 응답이 반환되기 전에 여러 상태가 진행됩니다.XMLHttpRequest가 완료된 순간을 캡처하려면 readystate 속성이 완료를 표시하는 4가 될 때 감지하도록 onreadystatechange 속성에 이벤트 처리기를 설정해야 합니다. 그 때 status 속성을 검사할 수 있습니다.

참고

XMLHttpRequest가 완료된 후 잠재적인 메모리 누수 문제를 방지하기 위해 onreadystatechange 속성을 null로 설정하는 것이 좋습니다.

이벤트 처리기인 익명 함수 내에서 완료를 확인한 후 status 속성을 검사하여 작업이 성공적인지 확인할 수 있습니다. 이 경우 만들기 작업으로부터의 응답 본문에 예상되는 것이 없기 때문에 예상되는 상태 값이 204 No Content입니다. 만들어진 거래처에 대한 URI는 OData-EntityId 머리글에 있고 getResponseHeader 메서드를 사용하여 액세스할 수 있습니다.

이것이 응답에 데이터를 반환할 것으로 예상된 다른 작업인 경우 200 OKstatus 값이 있고 함수는 XMLHttpRequest 응답에 JSON.parse를 사용하여 JSON 응답을 코드가 액세스할 수 있는 JavaScript 개체로 변환했을 것입니다.추가 정보:구문 분석 JSON 반환

상태가 예상된 값이 아닌 경우 오류이며 오류 개체는 응답의 오류 구문 분석에 설명된 속성과 함께 반환됩니다. 이 예는 message 속성에 액세스할 수 있도록 JSON.parse를 사용하여 XMLHttpRequestresponse 속성을 JavaScript 개체로 변환합니다.

XMLHttpRequest 보내기

마지막으로 XMLHttpRequestsend 메서드를 사용하여 필요한 모든 JSON 데이터를 포함해 요청을 보냅니다.JSON.stringify를 사용하여 JavaScript 개체를 보낼 때 요청의 본문에 포함할 수 있는 JSON 문자열로 변환합니다.

보낼 JSON 데이터 작성

앞의 예에서는 단일 속성 집합만을 사용하여 거래처 엔터티가 만들어집니다. 엔터티에 어떤 속성을 사용할 수 있는지 확인하려면 CSDL 메타데이터 문서, 해당 문서에서 생성된 문서 또는 해당 문서를 사용하여 생성된 코드를 살펴보아야 합니다. 모든 Microsoft Dynamics 365 조직에 포함된 시스템 비즈니스 엔터티의 경우 Web API EntityType Reference를 참조할 수 있습니다. 속성 이름은 소문자이고 Boolean, Number, String, Array, ObjectDate 등 다음 JavaScript 유형에 해당하는 단순 데이터 형식을 수락합니다.

참고

간단한 데이터 형식을 사용하는 유일한 예외는 BooleanManagedProperty ComplexType으로, 엔터티 메타 데이터 엔터티 및 웹 리소스, 템플릿, 보고서, 역할, 저장된 쿼리와 같이 솔루션 인식 데이터를 메타 데이터 엔터티에 저장하기 위해 사용되는 엔터티입니다. 이 속성은 비즈니스 데이터를 저장하는 엔터티에 대해 절대 사용되지 않습니다. 메타 데이터 엔터티는 많은 복잡한 형식을 사용하며 다양한 규칙을 따릅니다. 자세한 내용은 웹 API를 Dynamics 365 메타데이터와 함께 사용를 참조하십시오.

요청에 보낼 데이터를 작성하는 것은 일반적으로 보통 JavaScript 개체를 만들고 적절한 속성을 설정하는 단순한 작업입니다. 다음 코드는 JavaScript 개체를 속성 및 값으로 정의하는 두 가지 유효한 메서드를 보여 줍니다. 이 예는 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)"
};

이러한 개체가 정의된 방식에 관계없이 JSON.stringify를 사용한 후 둘 다 같은 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)"
}

JavaScript에 대한 보통 속성 이름 지정 지침을 따르지 않는 속성을 정의해야 하는 경우가 있습니다. 예를 들어 속성 이름에 @odata.bind를 추가해야 하는 엔터티를 만들고 관련된 엔터티에 해당하는 URL에 값을 설정할 때 단일 값 탐색 속성의 값을 설정하는 경우가 있습니다. 이 경우 속성을 앞의 예와 같이 대괄호 표기법 스타일로 정의해야 합니다.

메타데이터 엔터티 관련 작업을 수행하는 경우를 제외하고 엔터티 속성을 개체에 설정하지 않습니다. 메타데이터 엔터티의 경우 복잡한 유형이거나 열거형 값인 속성을 설정해야 하는 경우가 많습니다. 하지만 이것은 보통 비즈니스 엔터티와 공통이 아닙니다.

관련 엔터티를 만들 때 Array를 사용하여 컬렉션 값 탐색 속성의 값을 설정할 수 있지만 이것은 다소 특수한 작업입니다.추가 정보:한 번 작업으로 관련 엔터티를 만듭니다.

엔터티 유형 속성

매개 변수 유형이 crmbaseentity EntityType 또는 activitypointer EntityType 등의 엔터티에 대한 기본 유형을 나타내는 경우 엔터티를 작업에 게시하면 @odata.type 속성을 엔터티 유형의 전체 이름을 값으로 하여 포함해야 할 수 있습니다. 예를 들어 letter EntityType는 activitypointer에서 상속되기 때문에 엔터티의 유형을 "@odata.type": "Microsoft.Dynamics.CRM.letter"의 속성 및 값을 사용하여 명시해야 할 수 있습니다.

업데이트 작업에 대한 데이터 보내기

엔터티를 업데이트하는 경우 업데이트하려는 속성에 대한 속성 값만 설정하는 것이 중요합니다. 엔터티를 검색하고 검색한 인스턴스의 속성을 업데이트한 다음 업데이트 작업에 해당 인스턴스를 사용해서는 안 됩니다. 대신 새 개체를 만들고 업데이트하려는 속성에 대해서만 새 속성을 설정해야 합니다.

검색한 엔터티의 모든 속성을 복사하고 PATCH를 사용하여 업데이트만 하는 경우 값이 현재 값과 같더라도 보내는 각 속성이 업데이트로 간주됩니다. 엔터티 및 특성에 대해 감사를 활성화하는 경우 값에 실제 변경 내용이 없으면 데이터가 변경되었음을 표시합니다.추가 정보:기본 업데이트

구문 분석 JSON 반환

앞의 예에서 사용된 만들기 작업이 JSON 데이터를 반환하지 않지만 GET을 사용하는 대부분의 작업은 JSON을 반환합니다. 반환된 데이터 형식 대부분은 다음 코드 라인을 사용하여 JSON을 JavaScript로 변환할 수 있습니다.

var data = JSON.parse(this.response)

하지만 날짜가 포함된 데이터는 예를 들어 2015-10-25T17:23:55Z와 같은 문자열로 전달되기 때문에 문제가 됩니다. 이것을 JavaScriptDate 개체로 변환하려면 JSON.parse 함수에 대한 reviver 매개 변수를 사용해야 합니다. 다음은 날짜 구문 분석에 사용할 수 있는 함수의 예입니다.

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;
 };

이 함수를 적용하려면 여기에 표시된 것처럼 매개 변수로 포함합니다.

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

콜백을 사용하여 재사용 가능한 함수 만들기

특정 작업을 수행할 코드가 있는 경우 같은 코드를 반복하여 작성하지 않고 재사용할 수 있습니다. 다음 단계는 사용 가능한 옵션으로 작업을 수행할 함수가 포함된 JavaScript 라이브러리를 만드는 것입니다. 이 경우 만들기 작업에는 두 개의 변수가 있는데, 엔터티 집합 이름과 만들 엔터티의 JSON 정의입니다. 앞의 모든 코드를 작성하기보다는 몇 줄의 코드만 있으면 사용할 수 있는 함수에 같은 작업을 포함할 수 있습니다.

JavaScript를 사용한 비동기 작업은 전통적으로 비동기 작업에서 모든 반환 값을 포착하고 프로그램에서 논리를 계속하는 방법으로 콜백 함수를 사용해왔습니다. 앞에서 설명한 만들기 작업에 코드를 사용하는 목표는 다음 코드만을 사용하여 같은 작업을 수행하도록 하는 것입니다.

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

이 예에서 MyNameSpace.WebAPI는 사용하는 모든 함수에 대해 고유한 이름을 부여하는 최상의 방법을 나타냅니다.추가 정보:JavaScript 함수에 대한 고유 이름 정의

라이브러리에는 재사용이 가능한 개인 함수로 작업을 지원할 기회가 있도록 추가 작업에 대해 함수를 포함하려고 계획합니다. 다음 코드는 이를 보여주고 콜백을 사용하는 MyNameSpace.WebAPI.create 함수가 포함되는 라이브러리를 표시합니다.

"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);

이 라이브러리는 자체 실행 비동기 함수(자체 호출 비동기 함수 또는 즉시 호출 비동기 함수라고도 함) 내에서 함수를 정의하고 MyNameSpace.WebAPI 네임스페이스에 함수를 첨부하는 최상의 방법을 보여줍니다. 이렇게 하면 다른 코드에서 액세스할 수 없는 내부 함수를 정의할 수 있습니다.this의 일부로 정의된 모든 함수는 공용이며 비동기 함수 내에 있는 모든 함수는 공용 함수에 사용될 수 있지만 비동기 함수 외부의 코드에는 사용될 수 없습니다. 함수 내의 코드는 페이지의 다른 코드가 수정할 수 없습니다.

네임스페이스는 같은 네임스페이스를 사용하는 다른 코드를 덮어쓰지 않도록 정의되지만 해당 네임스페이스의 일부인 이름이 같은 모든 함수는 덮어씁니다. 이름이 같지 않은 한 네임스페이스에 추가 공용 함수를 추가하는 별도의 라이브러리를 만들 수 있습니다.

MyNameSpace.WebAPI.create 함수는 다음 매개 변수를 제공합니다.

이름

설명

entitySetName

만들려는 엔터티의 유형에 대한 엔터티 집합의 이름

entity

만들려는 엔터티에 대한 속성이 포함된 개체

successCallback

엔터티를 만들 때 호출할 함수 만들어진 엔터티의 Uri는 이 함수에 전달됩니다.

errorCallback

오류가 있을 때 호출할 함수 이 오류는 이 함수에 전달됩니다.

XMLHttpRequest 개체를 구성하는 코드는 이러한 매개 변수 값과 기본 조직 URI를 찾고 웹 API에 루트 URI를 맞출 URL을 추가할 내부 도우미 함수 getWebAPIPath를 사용하도록 수정되었기 때문에 포함할 필요가 없습니다. 만들어진 엔터티의 URI는 정의되어 있는 경우 successCallback에 전달됩니다. 마찬가지로 공용 errorHandler 함수는 반환되는 모든 오류의 구문 분석에 사용됩니다.errorHandler 함수는 onreadystatechange 이벤트에 대한 이벤트 처리기 내에서 호출되고 이것이 네임스페이스의 범위 내에 있지 않기 때문에 공용이어야 합니다. 전체 이름 MyNameSpace.WebAPI.errorHandler를 사용하여 호출해야 합니다.

Promise를 사용하여 재사용 가능한 함수 만들기

콜백은 전통적으로 비동기 작업에 사용되어왔지만 많은 개발자들은 들여쓰기로 인해 익명의 함수를 사용하는 코드가 페이지의 오른쪽으로 더 멀리 이동하도록 만들면 일련의 비동기 작업이 상호 기반으로 “둠 피라미드”를 형성하는 코드를 만들기 때문에 다소 불편하고 읽기와 디버깅을 하기 어렵다고 생각합니다. 익명의 함수가 아니라 명명된 함수를 사용하여 이 문제를 해결할 수 있지만 많은 개발자들은 promise가 제공하는 이점을 높이 평가합니다.Promise 개체는 아직 완료되지 않았지만 향후 완료될 것으로 예상되는 작업을 나타냅니다.

다양한 promise 구현을 제공하는 타사 라이브러리 및 JavaScript 프레임워크가 많습니다.JQuery는 지연된 개체를 통해 CommonJS Promises/A 설계에 따른 동작을 제공했고 다른 사용자는Promises/A+ 사양 준수를 요구합니다. 이러한 구현의 차이에 대한 설명은 이 항목의 범위를 벗어납니다. 이 섹션의 목표는 네이티브 XMLHttpRequest 개체를 사용하는 Microsoft Dynamics 365 웹 API에 대한 도우미 함수가 Microsoft Dynamics 365가 지원하는 대부분의 최신 브라우저에서 구현되는 네이티브 Promise 개체를 사용하도록 작성할 수 있는 방법을 호출하는 것입니다.Google Chrome 32, Opera 19, Mozilla Firefox 29, Apple Safari 8 및 Microsoft Edge 등의 브라우저는 네이티브 promise를 구현합니다.

참고

Internet Explorer 11은 네이티브 promise를 구현하지 않습니다. 네이티브 promise를 구현하지 않는 브라우저의 경우 polyfill을 제공할 별도의 라이브러리를 포함해야 합니다. polyfill은 브라우저에서 기본적으로 제공되지 않는 기능을 제공하는 코드입니다.Internet Explorer 11에 promise를 허용하는 polyfill 또는 라이브러리는 es6-promise, q.js, bluebird와 같습니다.

Promise를 사용하는 이점은 예를 통해 가장 잘 설명할 수 있습니다. 다음 코드는 MyNameSpace.WebAPI.create의 콜백 버전을 사용하여 거래처를 만든 다음 그와 관련된 세 가지 작업을 만듭니다.

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); });

이 예에서는 이러한 모든 레코드를 깊은 삽입을 사용한 단일 작업에서 만들 수 있다는 사실을 무시합니다.추가 정보:한 번 작업으로 관련 엔터티를 만듭니다.

콜백 코드는 코드 블록의 중간에 끝나기 때문에 어려운 과제입니다. 한편 promise를 사용하면 다음 코드와 같은 레코드를 만들 수 있습니다.

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); });

Promise를 사용하면 코드 흐름이 보존되고 단일 catch 함수에서 발생하는 모든 오류를 catch할 수 있습니다.

콜백이 promise를 사용하는 함수를 변환하는 것은 다음 코드 예와 같이 콜백 매개 변수를 제거하고 약간 수정된 XMLHttpRequest를 반환하는 작업입니다.

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));
});

콜백 매개 변수를 제거하는 것 외에, XMLHttpRequestPromise에 포함되고 오류의 결과를 성공 또는 오류 콜백으로 전달하지 않고 resolve or reject 매개 변수로 전달됩니다. 다음 코드는 MyNameSpace.WebAPI.create 함수가 포함된 전체 JavaScript 라이브러리를 나타냅니다. 남은 작업은 같은 패턴을 사용하여 재사용이 가능한 Web API 작업을 더 추가하는 일입니다.

"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);

참고 항목

Microsoft Dynamics 365 웹 API 사용
웹 리소스를 사용하여 Dynamics 365 데이터 작업
웹 API를 사용하여 작업 수행
웹 API 샘플(클라이언트 쪽 JavaScript)
원본간 리소스 공유에 OAuth를 사용하여 Microsoft Dynamics 365에 단일 페이지 응용 프로그램 연결

Microsoft Dynamics 365

© 2017 Microsoft. All rights reserved. 저작권 정보