手寫axios

Waxiangyu發表於2022-07-13

定義Axios類

class Axios{
  constructor(){
    this.interceptors={
      request:new InterceptorsManage,
      response:new InterceptorsManage
    }
  }

  request(config){
    //攔截器和請求組裝佇列
    //成對出現,一個成功一個失敗,失敗回撥暫不處理
    let chain=[this.sendAjax.bind(this),undefined];
    //請求攔截,呼叫request請求時,請求攔截在前,響應攔截在後
    this.interceptors.request.handlers.forEach(inter=>{
      chain.unshift(inter.resolve,inter.reject)
    })
    this.interceptors.response.handlers.forEach(inter=>{
      chain.push(inter.resolve,inter.reject)
    })

    //執行佇列,每次執行一對,並給promise最新的值
    let promise=Promise.resolve(config);
    while(chain.length>0){
      //promise狀態是resolved會執行then中第一個回撥
      //1.request.use(成功,失敗),
      //2.成功把resolve(config)成功結果給到sendAjax引數執行,
      //3.sendAjax執行成功,把結果response給response.use(成功)引數執行
      promise=promise.then(chain.shift(),chain.shift())
    }
    return promise;
  }
  sendAjax(config){
    return new Promise((resolve,reject)=>{
      const {url='',method='get',data={}}=config;
      //傳送請求
      let xhr=new XMLHttpRequest();
      xhr.open(method,url,true);
      xhr.onreadystatechange=()=>{
        if (xhr.status >= 200 && xhr.status <= 300 && xhr.readyState === 4) {
          resolve(xhr.responseText)
        }
      };
      if(config.cancelToken){
        console.log(111)
        //promise為resolve狀態時,取消請求
        //onCancel就是上面的resolvePromise也就是promise.resolve()
        config.cancelToken.promise.then(function onCancel(reason){
          if(!xhr){return}
          xhr.abort();//取消請求
          reject(reason)
          xhr=null;
        })
      }
      xhr.send(data)
    })
  }
};

定義請求方法

//定義get,post等方法,掛到Axios上
const methodsArr=['get','delete','head','options','put','patch','post'];
methodsArr.forEach(met=>{
  Axios.prototype[met]=function(){
    console.log('執行'+met+'方法');
    //處理單個方法,帶2個引數(url,config)
    if(['get','delete','head','options'].includes(met)){
      return this.request({
        method:met,
        url:arguments[0],
        ...arguments[1]||{}
      })
    }else{//put,post有data,3個引數(url,data,config)
      return this.request({
        method:met,
        url:arguments[0],
        data:arguments[1]||{},
        ...arguments[2]||{}
      })
    }
  }
});

定義攔截器類

//攔截器
class InterceptorsManage{
  constructor(){
    this.handlers=[];
  }

  use(resolve,reject){
    this.handlers.push({
      resolve,
      reject
    })
  }
}

定義取消請求類

//取消請求
class CancelToken{
  constructor(exactor){
    //將promise給cancel。防止多次重複cancel
    let resolvePromise;//promise例項的resolve方法
    this.promise=new Promise(resolve=>{
      resolvePromise=resolve;
    })
    this.reason=undefined;
    const cancel=message=>{
      if(this.reason){return}
      this.reason='cancel'+message;
      resolvePromise(this.reason);//改變this.promise為resolve狀態,=resolve('message')
    }
    exactor(cancel)
  }
  throwIfRequested() {
    if (this.reason) {
      throw this.reason
    }
  }
  static source(){
    let cancel;//等於上面的cancel,是一個函式
    const token=new CancelToken(function exactor(c){
      cancel=c;
    });
    return {
      token,
      cancel
    }
  }
}

把定義的方法綁到Axios類上

//繼承類的方法及屬性
function extend(to,from,ctx){
  for(let key in from){
    //繼承自身屬性,不繼承原型鏈,用hasOwnProperty判斷
    if(from.hasOwnProperty(key)){
      if(typeof from[key]==='function'){
        to[key]=from[key].bind(ctx)
      }else{
        to[key]=from[key]
      }
    }
  }
  return to;
};

匯出axios

//引用
function createInstance(){
  let context=new Axios();
  //用例項化的context物件去接替Axios類的的request方法,支援axios({...})方法
  let instance=Axios.prototype.request.bind(context);
  //繼承get,post,put等方法
  extend(instance,Axios.prototype,context);
  extend(instance,context)
  return instance;
}
let axios=createInstance();
//加攔截器

匯出

//引用
function createInstance(){
  let context=new Axios();
  //用例項化的context物件去接替Axios類的的request方法,支援axios({...})方法
  let instance=Axios.prototype.request.bind(context);
  //繼承get,post,put等方法
  extend(instance,Axios.prototype,context);
  extend(instance,context)
  return instance;
}

let axios=createInstance();
//加攔截器
axios.interceptors.request.use(function(config){
  console.log(config,'config')
  //傳送之前做什麼
  return config;
},function(err){
  //請求出錯
  return Promise.reject(error)
});
axios.interceptors.response.use(function(response){
  //響應資料做什麼
  return response;
},function(err){
  return Promise.reject(err)
})

//取消請求
axios.CancelToken=CancelToken;

node程式碼


var express = require('express');
var app = express();


//允許跨域訪問
app.all('*',function(req,res,next){
  res.header('Access-Control-Allow-Origin','*');
  res.header('Access-Control-Allow-Headers','Content-Type');
  res.header('Access-Control-Allow-Methods','*');
  res.header('Content-Type','application/json;charset=utf-8');
  next();
})
app.get('/index',function(req,res){
  data={
    'frontEnd':'前端',
    'sunny':'陽光'
  }
  setTimeout(()=>res.json(data),5000)
  
});

var server = app.listen(3000, function(){
    console.log("伺服器啟動");
});

html頁面


<html>
  <script type="text/javascript" src="./index.js"></script>
  <body>
    <button id="btn">傳送</button>

    <script>
      document.getElementById('btn').onclick= function(){
        const cancelToken=axios.CancelToken;

        const {token,cancel}=cancelToken.source();

        axios.get('http://localhost:3000/index',{cancelToken:token})
        .then(res=>console.log(res,'rrr'))
        .catch(e=>console.log(e,'eee'))

        setTimeout(()=>cancel('取消了~'))
      }
    </script>
  </body>
</html>