utils_xhr.mjs

/**
## /utils/xhr

Export the default xhr method to mapp.utils{}.

@module /utils/xhr
*/

/**
@function xhr

@description
The params object/string for the xhr utility method is required.

The params are assumed to the request URL if provided as a string argument.

The request params and response are stored in a Map() if the cache flag is set in the params object argument.

The method is assumed to be 'POST' if a params.body is provided.

@param {Object} params The object containing the parameters.
@property {string} params.url The request URL.
@property {string} [params.method=GET] The request method.
@property {string} [params.responseType=json] The XHR responseType.
@property {Object} [params.requestHeader={'Content-Type': 'application/json'}] The XHR requestHeader.
@property {string} [params.body] A stringified request body for a 'POST' request.
@property {boolean} [params.resolveTarget] Whether the target instead of target.response should be resolved.
@property {boolean} [params.cache] Whether the response should be cached in a Map().

@returns {Promise} A promise that resolves with the XHR.
*/
const requestMap = new Map();

export function xhr(params) {
  return new Promise((resolve) => {
    // Return if params are falsy.
    if (!params) {
      console.error(`xhr params are falsy.`);
      return;
    }

    // Set params as object with url from string.
    params = typeof params === 'string' ? { url: params } : params;

    // A request url must be provided.
    if (!params.url) {
      console.error(`no xhr request url has been provided.`);
      return;
    }

    // Check whether a request with the same params has already been resolved.
    if (params.cache && requestMap.has(params))
      return resolve(requestMap.get(params));

    // Assign 'GET' as default method if no body is provided.
    params.method ??= params.body ? 'POST' : 'GET';

    const xhr = new XMLHttpRequest();

    xhr.open(params.method, params.url);

    // Use requestHeader: null to prevent assignment of requestHeader.
    if (params.requestHeader !== null) {
      // Butter (spread) over requestHeader.
      const requestHeader = {
        'Content-Type': 'application/json',
        ...params.requestHeader,
      };

      Object.entries(requestHeader).forEach((entry) =>
        xhr.setRequestHeader(...entry),
      );
    }

    xhr.responseType = params.responseType || 'json';

    xhr.onload = (e) => {
      if (e.target.status >= 400) {
        resolve(new Error(e.target.status));
        return;
      }

      // Cache the response in the requestMap
      params.cache && requestMap.set(params, e.target.response);

      resolve(params.resolveTarget ? e.target : e.target.response);
    };

    xhr.onerror = (e) => resolve(new Error(e));

    xhr.send(params.body);
  });
}