Source: sputils.rest.js

(function () {
  // This is used to cache results
  // from grabbing the request digest.
  var requestDigest;

  // Simple AJAX request for fetching the request digest
  // from /_api/contextinfo. This is used as a fallback
  // for the one embedded in the SharePoint page.

  // Returns a promise resolving to the digest string.
  var requestFormDigest = function () {
    return contextInfo()
      .then(function (info) {
        return info.FormDigestValue;
      });
  };

  function contextInfo(webUrl) {
    var CONTEXT_INFO_API = '/_api/contextinfo';
    return post(webUrl + CONTEXT_INFO_API)
      .then(function (data) {
        return data.d.GetContextWebInformation;
      });
  }

  // Utility for grabbing the digest off the page in
  // an asynchronous manner. Solves the issue of script
  // running before page has loaded proper.
  // --
  // Returns a promise resolving to the digest string.
  var withRequestDigest = function (refresh) {
    return new Promise(function (resolve, reject) {
      if (requestDigest && refresh !== true) {
        resolve(requestDigest);
      } else {
        var rd = global.document.getElementById('__REQUESTDIGEST');
        if (rd !== null && rd.value !== 'InvalidFormDigest') {
          requestDigest = rd.value;
          resolve(requestDigest);
        } else {
          var tapCache = tap(function (digest) {
            // `tap` will pass the digest to the next handler
            requestDigest = digest;
          });
          requestFormDigest()
            .then(tapCache).then(resolve);
        }
      }
    });
  };

  // Gets the default config object for ajax requests.
  // Is asynchronous for consistency.

  // Returns a promise resolving to the ajax defaults.
  var getDefaults = function (url, config) {
    return new Promise(function (resolve, reject) {
      var defaults = {
        // If the URL is not absolute, get the missing part
        // from _spPageContextInfo
        url: url.indexOf('http') > -1 ? url : _spPageContextInfo.webAbsoluteUrl + url,
        method: 'GET',
        credentials: 'include',
        headers: {
          'Accept': 'application/json;odata=verbose'
        }
      };

      // fjs assign has the destination last (because curry), i.e. data flow:
      // config => defaults
      resolve(fjs.assign(config || {}, defaults));
    });
  };

  // Gets the default config object for ajax post requests,
  // which includes the getConfig and the request digest.

  // Returns a promise resolving to the ajax post defaults.
  var postDefaults = function (url, data, config) {
    return Promise.all([getDefaults(url), withRequestDigest()])
      .then(function (results) {
        var defaults = results[0],
            digest = results[1];

        var headers = fjs.assign(
          getval('headers', config) || {},
          getval('headers', defaults) || {},
          {
            'X-RequestDigest': digest,
            'Content-Type': 'application/json;odata=verbose;charset=utf-8'
          });

        var added = {
          method: 'POST',
          body: data
        };

        var cfg = fjs.assign(config || {}, added, defaults);
        cfg.headers = headers;
        return cfg;
      });
  };

  /**
   * Rest API get helper. Uses sane defaults to speak to the API. Additional
   * configuration can be passed with the config argument.
   * @function sputils.rest.get
   * @param {string} url an SP endpoint
   * @param {object} config additional configuration
   * @returns {Promise<object>} a promise that resolves to the response data
   * @example
   * sputils.rest.get('/_api/web/lists').then(function (data) {
   *   $.each(data.d.results, function (idx,el) {
   *     console.log(el);
   *   });
   * });
   */
  var get = function (url, config) {
    url = url || '/';
    return getDefaults(url, config)
      .then(function (defaults) {
        return fetch(defaults.url, defaults).then(jsonify);
      });
  };

  /**
   * Rest API post helper. Uses sane defaults to speak to the API. Additional
   * configuration can be passed with the config argument.
   * @function sputils.rest.post
   * @param {string} url an SP endpoint
   * @param {object} data the payload
   * @param {object} config additional configuration
   * @returns {Promise<object>} a promise that resolves to the response data
   * @example
   *
   * var data = {
   *   Title: 'REST API FTW',
   *   __metadata: { type: 'SP.Data.AnnouncementsListItem'}
   * };
   *
   * sputils.rest.post("/_api/Web/Lists/getByTitle('Announcements')/", data)
   *   .then(function (data) { console.log(data) });
   */
  var post = function (url, data, config) {
    data = typeof data === 'string' ? data : JSON.stringify(data);
    return postDefaults(url, data, config).then(function (defaults) {
      return fetch(url, defaults).then(jsonify);
    });
  };

  /**
   * Results from the standard SharePoint REST APIs come
   * wrapped in objects. This convenience function unwraps
   * them for you. See example use.
   * @function sputils.rest.unwrapResults
   * @param {object} object raw SP API response data
   * @returns {Promise<object>} unwrapped SP API data
   * @example
   * sputils.rest.get('/_api/web/lists')
   *   .then(sputils.rest.unwrapResults)
   *   .then(function (data) {
   *     sputils.fjs.each(function (el, idx) {
   *       console.log(el);
   *     }, data);
   *   });
   */
  var unwrapResults = function (object) {
    return object.d.results || object.d;
  };

  // If the given argument has a json method
  // we call it, otherwise just return the argument.
  var jsonify = function (result) {
    return typeof result.json === 'function' ? result.json() : result;
  };

  /** @namespace */
  sputils.rest = {
    get: get,
    post: post,
    withRequestDigest: withRequestDigest,
    unwrapResults: unwrapResults,
    contextInfo: contextInfo
  };
})();