[注:Image by Birgit Böllinger from Pixabay]

XmlHttpRequest

var xhr=new XMLHttpRequest();
xhr.open("GET",url);
xhr.responseType="json";

xhr.onload=function(){
    console.log(xhr.response);
}

xhr.onerror=function(){
    console.log("error");
}

xhr.send();
  • 在不重新加载页面的情况下更新页面
  • 在页面已加载后从服务器请求/接收数据
  • 在后台向服务器发送数据

ajax

$.ajax({
    type:"post",
    url:url,
    data:data,
    dataType:dataType,
    success:function(){},
    error:function(){}
})
  • 对原生 XHR 的封装
  • 增加了对 JSONP 的支持

缺点:

  • 针对 MVC 编程,不符合前端 MVVM 的浪潮
  • 架构不清晰,如果有多个请求或者依赖关系,容易形成回调地狱
  • 为了 ajax 引入 jQuery 不合理

axios

axios({
    method:"post",
    url:url,
    data:data,
}).then(response=>{
    console.log(response);
}).catch(err=>{
    console.log(err);
})
  • 从 node.js 创建 http 请求
  • 也是对原生 XHR 的封装,但是是 Promise 的实现版本,符合最新 ES 规范
  • 客户端支持防止 CSRF
  • 提供了一些并发请求的接口

缺点:

  • 只支持现代浏览器

fetch

fetch("/users.json",{
    method:"post",
    mode:"no-cors",
    data:{}
}).then(function(response){
    return response.json();
}).then(function(data){
    console.log(data);
}).catch(function(e){
    console.log("error");
})

换成更简洁的箭头函数的写法:

fetch("/users.json",{
    method:"post",
    mode:"no-cors",
    data:{}
}).then(response=>{
    return response.json();
}).then(data=>{
    console.log(data);
}).catch(e=>{
    console.log("error");
})

换成 es7 async/await 的写法:

try{
    let response=await fetch(url);
    let data=await response.json();
    console.log(data);
}catch(e){
    console.log("error",e);
}

Promise, generator/yield,await/async 都是现在和未来JS解决异步的标准做法,可以完美搭配使用。

总结,Fetch 优点:

  • 语法简洁
  • 基于标准 Promise 实现,支持 async/await
    • 因为旧版浏览器不支持 Promise,所以需要使用 polly-fill es6-promise
  • 同构方便
  • 对跨域的处理
    • 在配置中添加mode:"no-cors"即可

用 Fetch 遇到的坑:

  • Fetch 请求默认不带 cookie ,需要设置 fetch(url,{credentials:"include"})

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

  • 不能中断,没有 abort, terminate, onTImeout, cancel 方法

  • 不同于 XHR ,不能监测请求进度(但这样会比较简单)

我看了我们目前几个项目,都是基于 fetch 实现的,所以看了一下对上述坑的解决方案:

import { message, Modal } from "antd";
import { getLocal } from "common/LocaleProvider";
import omitEmpty from "omit-empty";
//import { removeUserInfo } from "utils/user";
import { getSearchParamValue } from "./url";

function getApiDomain() {
	const apiip = getSearchParamValue("apiip");
	return apiip || window.location.host;
}

function checkStatus(response: Response): Response {
	if (!response.ok) {
		const error = new Error(response.statusText);
		switch (response.status) {
			case 401:
				window.location.pathname = "/login.html";
				break;
			case 500:
				message.warn("server error");
				break;
			default:
				return response;
		}
		throw error;
	}
	return response;
}

export function getApiUrl(method: string) {
	method = method.replace(/\./g, "/");
	return `//${getApiDomain()}/api/${method}`;
}

export function doFetch(
	url: string,
	params: any,
	fetchMethod?: "POST" | "GET",
	headers?: HeadersInit
) {
	const method = fetchMethod ? fetchMethod : "post";
	params = omitEmpty(params);
	return fetch(url, {
		method,
		credentials: "include",
		headers,
		body: JSON.stringify(params)
	}).then(checkStatus);
}

const XLocale = new Headers({
	"x-locale": getLocal()
});

export function webapi<T>(method: string, params: any, apiURL?: string): Promise<T> {
	let url = apiURL ? apiURL : getApiUrl(method);

	// return doFetch(url, params)
	return doFetch(url, params, "POST", XLocale)
		.then(
			response =>
				response.json().then(json => ({
					data: json,
					status: response.status
				})) as Promise<{
					data: any;
					status: number;
				}>
		)
		.then<T>(response => {
			if (response.status === 200 && response.data.result.code !== 0) {
				if (response.data.result.code === 1000) {
					removeUserInfo();
					parent.location.href = "./login.html";
				}
				let msg = response.data.result.message;
				if (response.data.result.code === 500) {
					msg = msg ? msg : "server error";
				}
				console.error(msg);
				console.log(msg);
				return Promise.reject({
					status: 5001, // 专门为后端设置的专门接口信息错误
					message: msg
				});
			}
			if (response.status !== 200) {
				return Promise.reject({
					status: response.status,
					message: "后端接口信息:" + response.data.message
				});
			}
			return response.data;
		})
		.catch(err => {
			const msg = err.message || "";
			return Promise.reject({
				status: 5002,
				message: msg.match(/Failed to fetch/gi) ? `网络不稳定,请稍后重试` : msg
				// `接口:${method}挂了或者没有开启CORS跨域功能,请稍等!`
			});
		});
}

参考

  • https://segmentfault.com/a/1190000012836882