Ajax

Ajax

Asynchronous JavaScript and XML,通过JavaScript异步通信,从服务器获取XML文档(JSON 格式数据)并从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

  • 创建XMLHttpRequest实例
  • 发出HTTP请求
  • 接收服务器传回的数据
  • 更新网页数据

注意,AJAX只能向同源网址(协议、域名、端口都相同)发出HTTP请求,如果发出跨域请求,就会报错。

缺点同源限制

readyState

表示实例对象的当前状态,只读。

  • 0,请求未初始化,表示 XMLHttpRequest 实例已经生成,但是实例的open()方法还没有被调用。
  • 1,服务器连接已建立,表示open()方法已经调用,但是实例的send()方法还没有调用,仍然可以使用实例的setRequestHeader()方法,设定 HTTP 请求的头信息。
  • 2,请求已接受,表示实例的send()方法已经调用,并且服务器返回的头信息和状态码已经收到。
  • 3,请求处理中,表示正在接收服务器传来的数据体(body部分)。这时,如果实例的responseType属性等于text或者空字符串,responseText属性就会包含已经收到的部分信息。
  • 4,请求已完成且响应已就绪,表示服务器返回的数据已经完全接收,或者本次接收已经失败。
xhr.onreadystatechange = function(){
  /// 通信成功
  if (xhr.readyState === 4){
    /// 获取数据成功
    if (xhr.status === 200){
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};

status 

表示服务器回应的 HTTP 状态码。

  • 200, OK,访问正常
  • 403, Forbidden,禁止访问
  • 404, Not Found,未发现指定网址
  • 500, Internal Server Error,服务器发生错误

通常

4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求 

另外,statusText 属性返回一个字符串,表示服务器发送的状态提示。

upload

主要用于追踪文件上传的进度。

该属性得到一个对象,通过该对象的onprogress上的监听函数可以得知上传的进展。

function updateProgress (evt) {
  if (evt.lengthComputable) {
    var percentComplete = evt.loaded / evt.total;
  }
}

getAllResponseHeaders()

收到服务器响应后,获取服务器发来的所有 HTTP 头信息。

返回值为字符串,每个头信息之间使用CRLF分隔(回车+换行)[\r\n]

/// 获取
var headers = xhr.getAllResponseHeaders();
/// 处理
var arr = headers.trim().split(/[\r\n]+/);
var headerMap = {};
arr.forEach(function (line) {
  var parts = line.split(': ');
  var header = parts.shift();
  var value = parts.join(': ');
  headerMap[header] = value;
});

abort()

终止已经发出的 HTTP 请求。调用方法后,readyState属性变为4,status属性变为0。 

if (xhr) {
    xhr.abort();
    xhr = null;
  }

onreadyStateChange VS onload

  • load事件表示请求成功完成,服务器传来的数据接收完毕,即只有状态码为4时才回调一次函数。
  • onreadystatechange表示只要返回的状态码发生变化就回调一次函数。

Ajax请求

Ajax异步刷新方法

  • XMLHttpRequest
  • Fetch API
  • jQuery

目标可以是:

  • 本地数据(.json, .xml, .js)
  • 数据库

与POST相比,GET更简单也更快,在大部分情况下都适用。然而以下情况:

  • 无法使用缓存文件(更新服务器上的文件或数据库)
  • 向服务器发送大量数据(POST没有数据量限制)
  • 发送包含未知字符的用户输入时,POST比GET更稳定也更可靠

请使用POST请求。

  • 常规ajax请求
class MyComp extends React.Component {
  componentDidMount() {
    $.get(this.props.source, (result) => {
      var infoMsg = result[0];
      if (this.isMounted()) {
        this.setState({
          // ...设置组件状态
        });
      }
    }.bind(this));
  }
});
ReactDOM.render(
  <MyComp source="url" />, document.getElementById('root')
);
  • Promise对象  
class MyComp extends React.Component {
  componentDidMount() {
    this.props.promise.then(
      value => this.setState({...设置组件成功状态}),
      error => this.setState({...设置组件失败状态}));
  }
}
ReactDOM.render(
  <MyComp promise={$.getJSON('url')} />, document.getElementById('root')
);

下面分别简单介绍 ajax 的三种实现方式:

[1]. XMLHttpRequest(XHR)

  • API粗糙,配置和调用方式比较混乱
  • 浏览器的兼容性问题
  • 基于事件机制来跟踪状态变化,异步处理繁琐
  • 不符合关注分离(Separation of Concerns)的原则
  • 回调地狱

通过 onreadystatechange 属性来检测服务器的响应状态:

// 方法一 指定可调用的函数
xhr.onreadystatechange = onReadyStateChange;
function onReadyStateChange() {
  // do something
}

// 方法二 使用匿名函数
xhr.onreadystatechange = function(){
    // do the thing
}; 

[2]. jQuery实现的AJAX

  • 封装原生ajax代码,简单化
  • 封装jsonp,支持跨域访问
  • 回调地狱问题仍然存在

下面是常用的简单调用示例:

// GET
$.get('/api', function(res) {
  // do something
});

// POST
var data = {
  username: 'admin',
  password: 'root'
};
$.post('/api', data, function(res) {
  // do something
});  

[3]. Fetch  

  • API是基于标准Promise设计,支持链式调用
  • 回调较清晰,catch捕获异常
fetch(url, options).then(function(response) { 
   // handle HTTP response
}, function(error) {
   // handle network error
}).catch(function(e) {
   // exception error
});

Fetch坑

  • Fetch 请求默认不带 cookie,需要设置 options = {credentials: 'include'}

  • 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject

下面给出一个fetch的封装,摘自网上  

function _fetch(url, data, method = 'GET',options={}) {
    const body = o2s(data);
    let params = {
        method: method,
    };

    if (method === 'GET') { // 如果是GET请求,拼接url
        url += '?' + body;
    } else {
         params.body=body
    }

    if(options.cookie!=undefined){
        params.credentials='include'
    }

    if(options.headers!=undefined && typeof options.headers=="object"){
        params.headers=new Headers(options.headers);
    }
    else{
        params.headers=new Headers({
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded'
        });
    }

    fetch(url, params)
       .then(r => options.dataType=="text"?r.text():r.json())
       .then(r => r);
}

export function o2s(obj, arr = [], idx = 0) {
    for (let item in obj) {
        arr[idx++] = [item, obj[item]];
    }
    return new URLSearchParams(arr).toString();
}

export function get(url, data,,options={}) {
    return _fetch(url, data, 'GET',options);
}

export function post(url, data,options={}) {
    return _fetch(url, data, 'POST',options);
}

// 方法调用示例
post("/api/test",{title:"qwer"},{
    dataType:"json",
    cookie:true,
    headers:{
        'Accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded'
    }  
});

fetch加强版

  • generator/yield:待学习
  • async/await
async function() {
  try {
    let response = await fetch(url);
    let data = response.json();
    console.log(data);
  } catch (error) {
    console.log('Oops, error: ', error);
  }
}

await后面跟Promise对象,表示等待Promise resolve()才会继续向下执行。如果Promise被reject()或抛出异常,则会被外面的try...catch捕获。  

文件上传

HTML网页的<form>元素支持四种格式,向服务器发送数据:

  • POST方法,enctype=application/x-www-form-urlencoded,默认方法
  • POST方法,enctype=text/plain
  • POST方法,enctype=multipart/form-data
  • GET方法,enctype属性忽略

通常使用file控件上传文件,控件的multiple属性,标识可以一次选择多个文件。

<input type="file" id='input-file-select' name='input-file-name' multiple/>
/// 获取选中待传文件
var fileSelect = document.getElementById('input-file-select');
var files = fileSelect.files;
/// 组装FormData对象
var formData = new FormData();
files.forEach(function (file) {
  formData.append('input-file-name', file, file.name);    
});

除了使用FormData上传,也可以直接使用File API上传

var xhr = new XMLHttpRequest();
xhr.open('POST', myURL);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);

Ajax同源问题: CORS

除了服务器代理(浏览器请求同源服务器,再由后者请求外部服务),支持三种方法规避限制

[1]. JSONP

服务器与客户端跨源通信的常用方法,简单适用,服务端改造非常小。

缺点:只能发Get请求。但是,支持老式浏览器,可以向不支持CORS的网站请求数据。

基本思想:网页通过添加一个<script>元素,向服务器请求JSON数据,不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

[2]. WebSocket

一种使用 ws://(非加密)和 wss://(加密)作为协议前缀的通信协议,不实行同源政策。

优点:允许服务器端与客户端进行全双工(full-duplex)的通信。(HTTP仅支持客户端请求服务器)

WebSocket握手,建立在 TCP 协议之上。

[3]. CORS

跨源资源分享(Cross-Origin Resource Sharing),W3C标准, 跨源Ajax请求的 根本解决 方法。

  • 与JSONP相比,支持任何类型的请求
  • 整个CORS通信过程,浏览器自动完成

CORS请求分成两类

  • 简单请求:同表单请求
  • 非简单请求:先预检请求,不通过则提前拒绝,通过则正常请求
posted @ 2018-06-17 22:46  万箭穿心,习惯就好。  阅读(237)  评论(0编辑  收藏  举报