Home Reference Source Repository

js/util/_AjaxRequest.js

'use strict'

/**
 * AjaxRequest class
 * @access public
 */
export class AjaxRequest {
  /**
   * constructor
   * @access public
   * @constructor
   */
  constructor() {
    this.defaultOptions = {
      method: 'POST',
      async: true,
      data: null,
      user: null,
      password: null,
      mimeType: null,
      isJSONP: false,
      responseType: null,
      requestHeader: {}
    }

    this.jsonpScripts = []
    this.jsonpCallbackPrefix = 'jsonpCallback_'
  }

  get(url, options = {}) {
    options.method = 'GET'
    return this.request(url, options)
  }

  post(url, options = {}) {
    options.method = 'POST'
    return this.request(url, options)
  }

  jsonp(url, callbackParam = 'callback', options = {}) {
    let requestURL = url
    const script = document.createElement('script')
    const callbackFuncName = this._getNewFuncName(this.jsonpCallbackPrefix)

    if(!options.data){
      options.data = {}
    }
    options.data[callbackParam] = callbackFuncName

    const queryArray = []
    options.data.forEach((key, value) => {
      queryArray.push(encodeURI(key) + '=' + encodeURI(value))
    })
    requestURL += '?' + queryArray.join('&')

    const promise = new Promise((resolve, reject) => {
      window[callbackFuncName] = resolve
      script.addEventListener('error', reject)
    }).catch((error) => {
      delete window[callbackFuncName]
      document.head.removeChild(script)
      return Promise.reject(error)
    }).then((result) => {
      delete window[callbackFuncName]
      document.head.removeChild(script)
      return Promise.resolve(result)
    })

    script.src = requestURL
    document.head.appendChild(script)

    return promise
  }

  _getNewFuncName(prefix = '') {
    // ESLint prefers for(;;) more than while(true)
    for(;;){
      const funcName = prefix + Math.random().toString(16).slice(2)
      if(typeof window[funcName] === 'undefined'){
        return funcName
      }
    }
  }

  request(url, options = {}) {
    let requestURL = url
    let method = (typeof options.method === 'undefined' ? this.defaultOptions.method : options.method)
    const async = (typeof options.async === 'undefined' ? this.defaultOptions.async : options.async)
    let data = this.defaultOptions.data
    const user = (typeof options.user === 'undefined' ? this.defaultOptions.user : options.user)
    const password = (typeof options.password === 'undefined' ? this.defaultOptions.password : options.password)
    const mimeType = (typeof options.mimeType === 'undefined' ? this.defaultOptions.mimeType : options.mimeType)
    const header = (typeof options.requestHeader === 'undefined' ? this.defaultOptions.requestHeader : options.requestHeader)
    const isJSONP = (typeof options.isJSONP === 'undefined' ? this.defaultOptions.isJSONP : options.isJSONP)
    const responseType = (typeof options.responseType === 'undefined' ? this.defaultOptions.responseType : options.responseType)

    if(method !== 'POST' && method !== 'GET'){
      method = 'POST'
    }

    if(options.data){
      const dataArray = []
      if(options.data instanceof Map){
        options.data.forEach((key, value) => {
          dataArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
        })
      }else{
        Object.keys(options.data).forEach((key) => {
          const value = options.data[key]
          dataArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
        })
      }

      if(method === 'POST'){
        data = dataArray.join('&').replace(/%20/g, '+')
      }else{
        requestURL += '?' + dataArray.join('&').replace(/%20/g, '+')
      }
    }

    if(method === 'POST' && typeof header['Content-Type'] === 'undefined'){
      header['Content-Type'] = 'application/x-www-form-urlencoded'
    }

    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest()

      if(mimeType){
        xhr.overrideMimeType(mimeType)
      }
      if(responseType){
        xhr.responseType = responseType
      }

      if(user) {
        xhr.open(method, requestURL, async, user, password)
      }else{
        xhr.open(method, requestURL, async)
      }

      if(header){
        for(const key in header){
          // Reflect is not yet implemented...
          //if(Reflect.apply({}.hasOwnProperty, header, [key])){
          if({}.hasOwnProperty.call(header, key)){
            xhr.setRequestHeader(key, header[key])
          }
        }
      }

      xhr.onload = () => {
        if(xhr.readyState === 4 && xhr.status === 200){
          resolve(xhr.response)
        }else{
          reject(new Error(xhr.statusText))
        }
      }
      xhr.onerror = () => {
        reject(new Error(xhr.statusText))
      }
      xhr.send(data)
    })
  }
}

export default new AjaxRequest()