Ajax-Promise-Axios

作者: 果汁凉茶丶 | 来源:发表于2021-01-27 12:40 被阅读0次

      本文主要针对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
        })
      }
    }
    
    

    相关文章

      网友评论

        本文标题:Ajax-Promise-Axios

        本文链接:https://www.haomeiwen.com/subject/kwdsnktx.html