Ajax-Promise-Axios

  本文主要针对Ajax,Promise,Axios三者的本质、优缺点,使用实战做了阐述,抽象了应用办法,高度横向做了对比,一起进入学习吧~

一、Ajax

  AJAX:异步 JavaScript 和 XML,用来发送异步请求。有了Ajax之后,在无需重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
  Ajax是基于现有的Internet标准,联合使用了XMLHttpRequest,JS/DOM,CSS,XML等技术。
  它有个较大的缺陷就是业务逻辑需要在回调函数中执行,如果多个请求存在依赖关系,就会产生回调地狱问题,对读写理解困难。

1. 创建XHR

  由于IE7以下版本不支持XMLHttpRequest对象,使用 ActiveXObject,因此需要做兼容处理:

let request;
// code for IE7+, Firefox, Chrome, Opera, Safari
if (window.XMLHttpRequest) {
   request = new XMLHttpRequest();
} else { // code for IE6, IE5
   request = new ActiveXObject("Microsoft.XMLHTTP"); // 新建Microsoft.XMLHTTP对象
}

2. XHR对象常用方法

(1)XMLHttpRequest.open()

  初始化一个请求。已激活的请求再次调用此方法时,相当于调用了abort(),会中断上一个请求。
  当设置了async(第三个参数)true之后,请规定onreadystatechange事件中的就绪状态执行响应函数。

(2)XMLHttpRequest.send()

  向服务器发送http请求。如果open()中定义的是异步请求,则此方法会在请求发起后立刻返回;如果是同步,则在请求返回后才执行。

(3)XMLHttpReqeust.abort()

  当请求已经发出,则立刻中断请求。将readystate设置为0,立刻调用onreadystatechange方法执行回调函数。

(4)XMLHttpRequest.setReqeustHeader()

  用于给HTTP请求增加自定义请求头,在open()之后,send()之前定义。设置多个相同的请求头,在发起时会进行合并。

(5)XMLHttpRequest.getResponseHeader()

  根据名称获取HTTP请求头,如果需要一次请获取全部,请使用getAllHeetResponseHeader()方法。

3. 封装一个原生的Ajax请求

  结合以上的内容,封装一个兼容Post和Get的Ajax请求如下:

ajaxRequest(method, url, data, callback) {
  let xhr;
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }
  
  if (method.toLocalCase() === "get") {
    url = url + this.getParams(data);
    xhr.open(method, url, true);
    xhr.send();
  } else { // post
    xhr.open(method, url, true);
    xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
    data ? xhr.send(data) : xhr.send()
  }

  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if (xhr.state === 200) {
        // 约定返回字符串格式,如是xml格式使用responseXML
        callback(xhr.responseText);
      } else {
        reject(new Error(xhr.statusText))
    } else {
      if (xhr.readyStatue === 0) {
        alert("请求已取消");
      }
    }
  }
}

buildGetParams(obj) {
  if (!obj || obj.keys.length === 0) return '';
  let str = Object.keys(obj).reduce(function(prev, cur, index) {
    if (index === 1) {
      prev = prev + '=' + obj[prev]
    }
    return prev + (prev ? '&' : '') + cur.toString() + '=' + obj[cur]
  })
}

  模拟使用post调用

this.ajaxRequest('post', 'http://demo-domain:8080/test/ajax_post',{ name: 'test' }, function(data) {
  let result = JSON.parse(data)
  console.log(result)
});

二、Promise

  Promise对象由ES6提供,它表示一个尚未完成且预计在未来完成的异步操作。Promise 仅仅是一种决定异步任务状态确定时会出现什么结果的技术。它本身并不提供异步请求功能,更不能替代Ajax。

  Promise将异步请求的回调操作,改成了同步的链式写法,最直观的就是使用Promise处理ajax请求时,可以解决回调地狱问题;另外,Promise封装了统一的接口,使得控制异步操作变得更加简单。
  但它的缺点也比较明显,发起的Promise无法取消中断,其次,如果不设捕获异常的回调函数,Promise内部抛出的错误无法反应到外部出来。另外,未执行完成的Promise也无法确定是刚开始还是即将结束(即ajax的readyState在哪个过程)。

1. 创建Promise

  Promise自身提供了封装好的Promise()构造器,使用new关键字创建。我们可以自定义一个异步函数,传入Promise的构造器如:Promise(asyncFn)

let promise = new Promise((resolve, reject) => {
  // 执行结束需要使用resolve或reject将结果返回
})

2. Promise的基本API

  Promise有三种状态,分别是 pending:执行状态、fulfilled:已成功、rejected:已失败。
pending只能改变为fulfilledrejected其中一种,且状态一旦改变,将凝固不再改变。即使再次修改,该操作也会被忽略。

(1)Promise.prototype.then(onFulfilled, onRejected)

  当Promise的状态凝固后,就会调用 .then 方法。该方法接受两个回调函数,即成功回调函数resolve(),和失败回调函数reject(),并且返回一个Promise。通常情况下,我们通过一定的条件判断如res.code1表示成功,将结果作为参数即resolve(res.data)传递出去。否则使用reject(res.message)返回错误。
  当然,.then() 的第二个参数非必选,你也可以通过 .catch() API来实现

(2)Promsie.prototype.catch(onRejected)

  该API表示Promise从pending状态改变为rejected状态时调用,接受一个失败回调函数,返回一个Promise,使用效果同.then()的第二个参数。

(3)Promise.prototype.all()

  该方法接受一个数组,用于将多个Promise包装成一个Promise来执行,只有所有的请求状态都凝固后才改变自己的状态。并且只有在全部请求都返回fulfilled,该函数才返回 fulfilled

(4)Promise.prototype.race()

  竟速模式,该方法接受一个可遍历对象,将多个Promise包转成一个Promise来执行,最先成功(fulfilled)返回的请求当作该Promise的响应返回
......

3. 一种建议的Promise链式写法

  虽然.then() 方法很强大,但并不建议在其中定义处理异常方法,原因是如果在.then()onFulfilled回调函数中发生了异常,其内部定义的onRejected是无法捕获到的。但是在.then()后面使用.catch()则可以捕获到之前发生的所有异常。一种健康的写法是:

function taskA () { ... };
function taskB () { ... };
function finalTask () { ... };
var promise = new Promise();
promise
      .then(taskA)
      .catch(onRejectedA)
      .then(taskB)
      .catch(onRejectedB)
      .then(finalTask)

】不要使用 promise.then(taskA).then(taskB).then(finalTask).catch(onRejected),虽然这么定义所有的异常也都会被捕获到,但如果是taskA发生了异常,那taskB,fianlTask也将不被执行。

4. 使用Promise写法实现Ajax请求

  为了简便以下只以Get请求为例,

getRequest(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open('get', url, true);
    xhr.send();
  
    xhr.onreadystatechange() {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(new Error(xhr.statusText))
        }
      }
    }
  })
}

// 调用
this.getRequest("http://demo-domain:8080?name=test").then(resp => {
  console.log(resp)
})

三、Axios

  Axios是一个基于Promise的http库,支持node端和浏览器端,支持拦截器、自定义请求头、取消请求等高级配置,支持自动转换JSON,也支持npm包的使用。总之,优点多多,用就对了...
  针对Axios,在本文笔者不想写简单通用的功能,如有需要可以去axios官网学习了解。笔者想阐述更高效的用法如下

1. 自定义axios实例

  使用自定义配置新建一个简单的axios实例

let request = axios.create({
  baseURL: 'http://some-domain.com/api/',
  timeout: 1000,
  headers: { 'X-Custom-Header': 'footbar' }
})

2. 常用axios的请求配置

  以下配置中,只有 url 是必须的,如果没有配置method,将默认使用get方法。以下为常用的配置,更多请参见官网。

  • url: '/user' 用户请求服务器资源的 URL
  • method: 'post' 创建请求时使用的方法
  • baseURL: 'http://demo-domain:8080/api/' 自动加在URL(非绝对路径时)前的路径
  • headers: { 'x-Requested-With': 'XMLHttpRequest' } 自定义请求头
  • params: { 'ID': '12345' } 与请求一起发送的URL参数,当method指定为GET时使用
  • data: { 'name': 'zhangfs' } 请求主体参数,用于PUT,POST,PATCH方法
  • timeout: 8000 请求超时时间,0表示无超时;超过时间请求被中断
  • withCredentials: false 请求跨域时是否需要使用凭证
  • auth: { username: 'zhangfs' } 用于HTTP基础验证,将复写Authorization头
  • proxy: {
    host: '127.0.0.1',
    port: 9000,
    } 代理服务器的主机名与端口,将设置Proxy-Authoriation
  • cancelToken: new CancelToken(function(cancel) { ... }) 指定取消请求的cancel token
全局默认配置

  使用axios.defaults.xxx来配置;注意在实例中配置的优先级高于默认配置优先级

let request = new axios({
  baseURL: 'http://demo-domain.com:8080/api'
})
request.defaults.header.common['Authorization'] = AUTH_TOKEN
request.defaults.header.post['Content-Type'] = 'application/x-www-form-urlencoded'

3. 请求拦截器

  在请求或响应被 then 或 catch 处理前拦截它们。如果自定义了axios实例如request,则使用 request.interceptors.xxx

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

当然,也可以对拦截器进行移除,此时需要对拦截器显命名

var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

4. 取消请求

  ajax调用请求的abort()通过改变readyState=0来进行中断请求,axios则通过cancel token取消。

Axios 的 cancel token API 基于cancelable promises proposal,它还处于第一阶段。

  可以使用 CancelToken.source 工厂方法创建 cancel token

var CancelToken = axios.CancelToken;
var source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

  还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token

var CancelToken = axios.CancelToken;
var cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});

// 取消请求
cancel();

值得注意的是,可以使用同一个cancel token取消多个请求。

5. 一个axios实战实例

  请求封装文件service.js

const service = axios.create({
  headers: {},
  baseURL: Vue.prototype.configer.baseURL,
  timeout: 8000,
  withCredentials: true
})
service.interceptors.request.use(
  config => {
    if (config.method === 'get') {
      if (config.url.indexOf('?') < 0) {
        config.url += '?r=' + new Date().getTime()
      } else {
        config.url += '&r=' + new Date().getTime()
      }
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
service.interceptors.response.use(
  response => {
    // 处理返回体
    const result = response.data
    const code = Number(result.code)
    /**
     * Desc: 接口请求业务异常统一处理
     * Desc: 如:50008: 非法Token; 50012: 第三端登录; 50014: Token已过期;
     */
    if (code === 3002 || code === 50008 || code === 50012 || code === 50014 || code === 302) {
      alert('您已退出登录')
    } else {
      if (result.code && result.code !== '200') {
        console.log(result.message)
      }
      return result
    }
  }, errorFn // HTTP Code异常统一处理
)

  api封装文件 api.js

import service from '@/plugins/service'
import axios from 'axios'
const CancelToken = axios.CancelToken

export function queryDemoApi(data, _this) {
  return request({
    url: '/packageRoute/queryDemoApi',
    method: 'post',
    data,
    cancelToken: new CancelToken(function executor(c) {
      _this.cancelAjax = c
    })
  })
}

  组件内使用 info.vue

data() {
  return {
    cancelRequest: null
  }
}
method: {
  pageTriggleSearch(username) {
    if (typeof this.cancelRequest === 'function') {
      this.cancelRequest()
    }
    this.queryInfoApi({ name: username}, this).then((data) => {
      this.cancelRequest = null
      // do something else
    })
  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容