CodingMeUp / AboutFE

知识归纳、内容都在issue里
74 stars 14 forks source link

7、请求相关 #8

Open CodingMeUp opened 6 years ago

CodingMeUp commented 6 years ago

fetch

之前在项目中用到了这个fetch 来代替Ajax 进行网络请求。也踩了不少的坑,在这边列举出来以及他的解决方法。

如何保持每次请求的会话一致

在用fetch进行网络请求的时候,发现每次请求到服务端的时候,他的sessionId 都是不一样的,后面排查原来是在请求的时候fetch默认是不会带上本地jsessionId,以至于服务端无法接收到,所以会重新创建一个新的session。

解决办法:
var init = {
  credentials: 'include' // 请求带上cookies,是每次请求保持会话一直
  ...       
}

兼容性,支持IE10+、谷歌、火狐等

由于fetch是一个新技术,有些旧的浏览器对它并不支持,这时候要怎么兼容?

解决办法:

引入一个额外的补丁es6-promise.js可以使它很好的支持IE9以上的版本,那IE8呢?IE8 需要改fetch.js源码才能支持 fetch.js 只需改两处:

...
try{  
  Object.getOwnPropertyNames(headers).forEach(function(name) {  
    this.append(name, headers[name])  
  }, this)  
}catch(e){  
  var a = [];  
    for (var i in headers) {  
    if (headers.hasOwnProperty(i)) {  
      a.push(i); // 输出 foo asj  
    }  
  };  
  a.forEach(function(name) {  
    this.append(name, headers[name])  
  }, this)  
 }  
}

Headers.prototype.forEach = function(callback, thisArg) {  
  try{  
    Object.getOwnPropertyNames(this.map).forEach(function(name) {  
      this.map[name].forEach(function(value) {  
        callback.call(thisArg, value, name, this)  
      }, this)  
    }, this)  
  }catch(e){  
    var a = [];  
    for (var i in this.map) {  
      if (this.map.hasOwnProperty(i)) {  
        a.push(i); // 输出 foo asj  
       }  
    };  
    a.forEach(function(name) {  
      this.map[name].forEach(function(value) {  
        callback.call(thisArg, value, name, this)  
      }, this)  
    }, this)  
  }  
} 
...

try里面的是源码本身的,catch里面的是小编额外加的。细心的会发现这两处改的是一样的,都是对getOwnPropertyNames进行替换。但是IE8也不支持forEach为啥没有替换 呢? 其实有替换了,只是不再这边体现。我把它写进了Array.prototype里面了,其实fetch.js里面也用到了indexOf,它在IE8下也是不支持,我把它一并的放在Array.prototype;如下:

Array.prototype.forEach = function(callback, thisArg) {  
    var T, k;  
    if (this == null) {  
        throw new TypeError(" this is null or not defined");  
    }  
    var O = Object(this);  
    var len = O.length >>> 0; // Hack to convert O.length to a UInt32  
    if ({}.toString.call(callback) != "[object Function]") {  
        throw new TypeError(callback + " is not a function");  
    }  
    if (thisArg) {  
        T = thisArg;  
    }  
    k = 0;  
    while (k < len) {  
        var kValue;  
        if (k in O) {  
            kValue = O[k];  
            callback.call(T, kValue, k, O);  
        }  
        k++;  
    }  
};

Array.prototype.indexOf = function(elt /*, from*/){
    var len = this.length >>> 0;
    var from = Number(arguments[1]) || 0;
    from = (from < 0)
            ? Math.ceil(from)
            : Math.floor(from);
    if (from < 0)
        from += len;
    for (; from < len; from++)
    {
        if (from in this &&
            this[from] === elt)
        return from;
    }
    return -1;
};
CodingMeUp commented 6 years ago

Axios使用及源码分析

背景

工程院推进的React脚手架使用Axios网络库,Vue2.0也推荐使用。结合平常开发过程中的使用,总结遇到的问题以及分析部分核心源码。

Axios介绍

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

它有以下特性:

Axios基本使用

一般使用请求如下,符合Restful风格

axios.request(config)
axios.get(url, config)
axios.delete(url, config)
axios.head(url, config)
axios.post(url, data, config)
axios.put(url, data, config)
axios.patch(url, data, config)

config配置参数可自定义:
config = {
   baseURL: 'http://esp-homework-api',   //请求域名
   headers: XXX,   //请求头,可添加自定义参数
   ignoreErrorCodes: ['XXX','XXX'],  // 全局统一拦截时,如果配置该参数,则不会走错误拦截处理
   methods: 'POST',    //没设置methods时,默认是GET请求
   timeout: 500
}

Axios实际上是一个Promise,所以上述请求等同于下面的写法: Promise.all([getUser1(), getUser2()]).then(([data1, data2])=> { 两个请求都完成时,处理逻辑 })


- Axios请求返回的内容
````js
{
  data:{},      //真实返回数据,前端需要处理的
  status:200,
  statusText:'OK',
  headers: {},
  config: {}
}

可以测试下请求返回数据
axios.get('/user/818118')
  .then(function(res){
    console.log(res.data);
    console.log(res.status);
    console.log(res.statusText);
    console.log(res.headers);
    console.log(res.config);
  })
网络请求发出去之前进行拦截:

    axios.interceptors.request.use(function(config){
      return config;
    },function(err){
      return Promise.reject(error);
    });

    实际使用场景,可以设置auth参数:
    axios.interceptors.request.use(function(config=> {
      config.headers['Authorization'] = 'xxx'  
    })

网络请求返回之前进行拦截:

    axios.interceptors.response.use(function(res){
      return res;
    },function(err){
      return Promise.reject(error);
    });

    实际使用场景,因为axios实际返回是一个结构体,包含data数据,所以我们可以直接拦截返回给前台调用:
    axios.interceptors.response.use(function(response=> {
      return response.data
    })

源码分析

主要分析拦截器的相关源码,在这之前,我们有必要回顾下Promise的知识

第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB


整个拦截的过程,大概如下:

interceptors.request -> request -> interceptors.response -> response

我们现在来看下Axios类的定义
````js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

结合上面的使用方式axios.interceptors.request.use,interceptors主要是由一个InterceptorManager类的实例实现。

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

InterceptorManager的功能简单地讲,只是通过一个数组来维护拦截器,每个拦截器的两个参数分别作为Promise中的resolve和reject

InterceptorManager的使用是在Axios类中

Axios.prototype.request = function request(config) {

  // 挂载interceptor中间件
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

当执行request请求时,会遍历之前定义的interceptors拦截器,通过一个数组chain维护,经过遍历逻辑后,chain变量变为:

[interceptor.request.fulfilled, interceptor.request.rejected, 
dispatchRequest, undefined, 
interceptor.response.fulfilled, interceptor.response.rejected]

while方法循环包装成一个promise返回:
Promise.resolve(config)
    .then(interceptor.request.fulfilled, interceptor.request.rejected)
    .then(dispatchRequest, undefined)
    .then(interceptor.response.fulfilled, interceptor.response.rejected)

这样就是之前我们说的promise嵌套使用。

开发过程中,有使用过实例化axios的方法

    var instance = axios.create({
      baseURL:"https://some-domain.com/api/"});

但是新实例化的对象不会执行之前定义的拦截器方法,我们分下下创建实例的源码


function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);

  return instance;
}

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

因为重新实例化后,不会将原先定义的参数同步过来,需要自己重新配置

总结

Axios的有点在于既能在web,又能在node中使用,而且轻量级。我们在阅读源码时,可以学习它的封装思路,有助于在工程中实践。