New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

自实现之Ajax库 - XMLHttpRequest实现 #1

Open
littlewin-wang opened this Issue Dec 4, 2017 · 1 comment

Comments

Projects
None yet
1 participant
@littlewin-wang
Copy link
Owner

littlewin-wang commented Dec 4, 2017

自入行前端一来各种项目里有很多 Ajax 应用案例,大多数情况下都考虑了用 axios 等库来实现需求,直到打包的时候才 WTF,为什么一个简简单单的 Ajax 需求要耗费我几十 KB 的打包空间?

本文先以兼容性较好的 XMLHttpRequest (Level 2) 标准入手,一步步打造符合自己使用需求的Ajax库。

背景知识

首先,请大家阅读上述两篇文章,掌握XMLHttpRequest的常用属性和方法。
接下来的阅读我将默认您已经会用XMLHttpRequest啦。

兼容性

1749021644-569b9fb8d3b4a_articlex

一些后期需要 polyfill 的功能点

  1. IE8/IE9、Opera Mini 完全不支持xhr对象
  2. IE10/IE11部分支持,不支持 xhr.responseType为json
  3. 部分浏览器不支持设置请求超时,即无法使用xhr.timeout
  4. 部分浏览器不支持xhr.responseType为blob

实现思路

配置参数

首先确定需要参数化的一些配置属性

  • url: 目标地址, 必须
  • headers: 请求头属性对象 - {header_name: header_value, ...}
  • body:
    • string化的参数序列 ('application/x-www-form-urlencoded'较为常用,备选)
    • FormData (未设置 Content-Type 的默认值)
  • method: 'GET', 'POST', etc.
  • cors: If your using cross-origin, you will need this true for IE8-9

回调函数参数

确定回调函数参数

  • statusCode
    • null or 0,考虑异常
  • response
    • 根据 Content-Type 输出对象
    • 或者中断后输出 "Abort"
    • 或者超时后输出 "Timeout"
    • 或者其他错误输出 "Error"

代码

// xhr合法属性
var reqfields = [
  'responseType', 'withCredentials', 'timeout', 'onprogress'
]

const ajax = function (params, callback) {
  var headers = params.headers || {}
    , body = params.body
    , method = params.method || (body ? 'POST' : 'GET')
    , called = false

  // xhr对象generate
  var req = getRequest(params.cors)

  // 回调函数规范输出
  function cb(statusCode, responseText) {
    return function () {
      if (!called) {
        callback(req.status === undefined ? statusCode : req.status,
                 req.status === 0 ? "Error" : (req.response || req.responseText || responseText),
                 req)
        called = true
      }
    }
  }

  req.open(method, params.url, true)

  var success = req.onload = cb(200)
  req.onreadystatechange = function () {
    if (req.readyState === 4) success()
  }
  req.onerror = cb(null, 'Error')
  req.ontimeout = cb(null, 'Timeout')
  req.onabort = cb(null, 'Abort')

  if (body) {
    // 却省设置 FormData => 'application/x-www-form-urlencoded' 
    if (!window.FormData || !(body instanceof window.FormData)) {
      setDefault(headers, 'Content-Type', 'application/x-www-form-urlencoded')
    }
  }

  // xhr对象属性配置
  for (var i = 0, len = reqfields.length, field; i < len; i++) {
    field = reqfields[i]
    if (params[field] !== undefined)
      req[field] = params[field]
  }

  // xhr对象header配置
  for (var field in headers)
    req.setRequestHeader(field, headers[field])

  req.send(body)

  return req
}

function getRequest(cors) {
  // IE 8 and 9 polifill
  if (cors && window.XDomainRequest && !/MSIE 1/.test(navigator.userAgent))
    return new XDomainRequest
  if (window.XMLHttpRequest)
    return new XMLHttpRequest
}

function setDefault(obj, key, value) {
  obj[key] = obj[key] || value
}

Demo

ajax({
  method: 'GET',
  responseType: 'json',
  url: 'https://cnodejs.org/api/v1/topics',
  headers: {
    'Content-Type': 'application/json'
  }
}, function (code, responseText) {
  console.log(code)
  console.log(responseText)
})
{
"success": true,
"data": [ ... ] // 40 Items
}
@littlewin-wang

This comment has been minimized.

Copy link
Owner Author

littlewin-wang commented Dec 4, 2017

占坑补测试用例

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment