官方文档地址:http://www.axios-js.com/zh-cn/docs/

Axios的理解和使用

1. json-server服务搭建
  • 下载包:npm i json-server -g
  • 创建 db.json文件
  • 启动服务 json-server --watch db.json

db.json

{
  "post": [
    {
      "id": 1,
      "title": "json-server",
      "author": "typicode"
    }
  ],
  "comments": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    }
  ],
  "profile": {
    "name": "typicode"
  }
}

可以请求的URL

http://localhost:3000/post
http://localhost:3000/comments
http://localhost:3000/profile

带参数的请求参考:https://github.com/typicode/json-server

2. axios的特点
  • 基于 xhr + promise 的异步 ajax 请求库
  • 浏览器端/node 端都可以使用
  • 支持请求/响应拦截器
  • 支持请求取消
  • 请求/响应数据转换
  • 批量发送多个请求
3. axios的基本使用

get请求

// axios(config)
axios({
  method: "GET",
  url: "http://localhost:3000/posts/2",
}).then((response) => {
  console.log(response);
});

post请求

// axios(config)
axios({
  method: "POST",
  url: "http://localhost:3000/posts",
  //请求体
  data: {
    title: "火焰戎装",
    author: "水千丞",
  },
}).then((response) => {
  console.log(response);
});
// axios.post(url[, data, config])
axios.post({"http://localhost:3000/posts", {
    title: "火焰戎装",
    author: "水千丞",
  }).then((response) => {
    console.log(response);
  });

put请求

// axios(config)
axios({
  method: "PUT",
  url: "http://localhost:3000/posts/3",
  //请求体
  data: {
    title: "谁把谁当真",
    author: "水千丞",
  },
}).then((response) => {
  console.log(response);
});

delete请求

// axios(config)
axios({
  method: "DELETE",
  url: "http://localhost:3000/posts/3",
}).then((response) => {
  console.log(response);
});
4. axios 常用语法
  • axios(config): 通用/最本质的发任意类型请求的方式
  • axios.request(config): 等同于 axios(config)
  • axios(url[, config]): 可以只指定 url 发 get 请求
  • axios.get(url[, config]): 发 get 请求
  • axios.delete(url[, config]): 发 delete 请求
  • axios.post(url[, data, config]): 发 post 请求
  • axios.put(url[, data, config]): 发 put 请求
5. 响应结果结构
  • config:请求配置对象
  • data:响应体的内容对象
  • headers:响应头对象
  • request:原生的AJAX请求对象(XMLHttpRequest实例对象)
  • status:响应状态码
  • statusText:响应状态字符串
6. axios的默认配置

所有的配置项都可以设置默认配置

axios.defaults.method = "GET";
axios.defaults.baseURL = "http://localhost:3000";
axios.defaults.timeout = 3000;

在后续使用中如果传入的配置项没有配置,就会使用默认配置

7. axios创建实例对象发送请求
const comments = axios.create({
  baseURL: "http://localhost:3000",
  timeout: 3000,
});
comments({ url: "/comments", method: "GET" }).then((response) => {
  console.log(response);
});
// 也可以使用get方法指定请求方式
comments.get("/comments").then((response) => {
  console.log(response);
});

这种方式类似于配置多组默认配置,可以减少一些重复配置项的书写

8. axios拦截器

配置拦截器

axios.interceptors.request.use(
  (config) => {
    console.log("请求拦截器...成功1");
    return config;
    // throw "参数出了点问题";
  },
  (error) => {
    console.log("请求拦截器...失败1");
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  (response) => {
    console.log("响应拦截器...成功1");
    return response.data;
  },
  (error) => {
    console.log("响应拦截器...失败1");
    return Promise.reject(error);
  }
);

发送请求

axios("http://localhost:3000/comments")
  .then((response) => {
    console.log("自定义成功回调");
  })
  .catch((reason) => {
    console.log("自定义失败回调");
  });

配置完成的输出结果是:

请求拦截器...成功1
响应拦截器...成功1
自定义成功回调

也可以配置多个拦截器,需要注意的是:后配置的请求拦截器回调先执行,响应拦截器按照配置的顺序执行

请求拦截器...成功2
请求拦截器...成功1
响应拦截器...成功1
响应拦截器...成功2
自定义成功回调

可以在拦截器的回调中对请求的配置型和响应的数据进行处理

axios.interceptors.request.use(
  (config) => {
    console.log("请求拦截器...成功1");
    config.params = { a: 100 };
    return config;
  },
  (error) => {
    console.log("请求拦截器...失败1");
    return Promise.reject(error);
  }
);
axios.interceptors.request.use(
  (config) => {
    console.log("请求拦截器...成功2");
    config.timeout = 3000;
    return config;
  },
  (error) => {
    console.log("请求拦截器...失败2");
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  (response) => {
    console.log("响应拦截器...成功1");
    return response.data;
  },
  (error) => {
    console.log("响应拦截器...失败1");
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  (response) => {
    console.log("响应拦截器...成功2");
    return response[0].id;
  },
  (error) => {
    console.log("响应拦截器...失败2");
    return Promise.reject(error);
  }
);

响应拦截器回调按顺序,上一个回调的返回值是下一个回调收到的参数

问题:在每次进入回调前打印收到的参数,对于请求拦截器,在回调2调用时回调1还没有调用,但此时可以观察到回调1对于配置对象的修改

9. 取消请求
let cancel = null;
$btns.eq(0).on("click", () => {
  // 如果上一次的请求还没有结束,直接取消重新进行请求
  if (cancel) {
    cancel();
  }
  axios({
    method: "GET",
    url: "http://localhost:3000/comments",
    cancelToken: new axios.CancelToken((c) => {
      cancel = c;
    }),
  }).then((response) => {
    console.log(response);
    cancel = null;
  });
});
$btns.eq(1).on("click", () => {
  cancel();
});

源码解析

1. 模拟axios对象的创建过程
function Axios(config) {
  // 默认配置
  this.defaults = config;
  // 拦截器
  this.intercepters = {
    request: {},
    response: {},
  };
}
// 原型上要添加相关的方法
Axios.prototype.request = function (config) {
  console.log("发送 Ajax 请求,请求的类型为:" + config.method);
};
Axios.prototype.get = function (config) {
  return this.request({ method: "GET" });
};
Axios.prototype.post = function (config) {
  return this.request({ method: "POST" });
};
//
function createInstance(config) {
  // 实例化一个对象:此时可以作为对象使用,但是不能当做函数使用
  let context = new Axios(config);
  // 创建请求函数:此时是一个函数,不能够作为对象使用
  let instance = Axios.prototype.request.bind(context);
  // 为了解决问题:就要将 Axios.prototype 中的方法添加到 instance 中
  Object.keys(Axios.prototype).forEach((key) => {
    instance[key] = Axios.prototype[key].bind(context);
  });
  // 还要将原本的axios对象上的defaults和intercepters也添加上
  Object.keys(context).forEach((key) => {
    instance[key] = context[key];
  });
  return instance;
}
let axios = createInstance();
// 此时可以作为函数发送请求,也可以作为对象发送请求
axios.get();
axios.post();
axios({ method: "Get" });
2. 模拟实现 Axios 发送请求
function Axios(config) {
  this.config = config;
}

Axios.prototype.request = function (config) {
  // 创建一个promise对象
  let promise = Promise.resolve(config);
  // 声明一个数组
  let chains = [dispatchRequest, undefined];
  // 循环处理数组
  let result = promise.then(chains[0], chains[1]);
  // 返回promise结果
  return result;
};

function dispatchRequest(config) {
  // 调用适配器发送请求
  return xhrAdapter(config).then(
    (response) => {
      // 处理请求的结果
      return response;
    },
    (error) => {
      throw error;
    }
  );
}

function xhrAdapter(config) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(config.method, config.url);
    xhr.send();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve({
            config: config,
            data: xhr.response,
            headers: xhr.getAllResponseHeaders(),
            request: xhr,
            status: xhr.status,
            statusText: xhr.statusText,
          });
        } else {
          reject(new Error("请求失败,失败的状态码为" + xhr.status));
        }
      }
    };
  });
}

let axios = Axios.prototype.request.bind(null);

axios({
  method: "GET",
  url: "https://autumnfish.cn/search?keywords=你",
}).then((value) => {
  console.log(value);
});
3. 模拟实现 Axios 拦截器功能
function Axios(config) {
  this.config = config;
  this.interceptors = {
    request: new InterceptorsManager(),
    response: new InterceptorsManager(),
  };
}

// 拦截器管理器构造函数
function InterceptorsManager() {
  this.handlers = [];
}

InterceptorsManager.prototype.use = function (fullfilled, rejected) {
  this.handlers.push({ fullfilled, rejected });
};

Axios.prototype.request = function (config) {
  let promise = Promise.resolve(config);
  const chains = [dispatchRequest, undefined];
  // 将请求拦截器的回调压入到 chains 的前面
  this.interceptors.request.handlers.forEach((value) => {
    chains.unshift(value.fullfilled, value.rejected);
  });
  this.interceptors.response.handlers.forEach((value) => {
    chains.push(value.fullfilled, value.rejected);
  });
  // 遍历调用:请求拦截器 请求 相应拦截器
  while (chains.length > 0) {
    promise = promise.then(chains.shift(), chains.shift());
  }
  return promise;
};

function dispatchRequest() {
  return new Promise((resolve, reject) => {
    resolve({
      status: 200,
      statusText: "OK",
    });
  });
}

let context = new Axios({});
let axios = Axios.prototype.request.bind(context);
Object.keys(context).forEach((key) => {
  axios[key] = context[key];
});

axios.interceptors.request.use(
  (config) => {
    console.log("请求拦截器...成功1");
    config.params = { a: 100 };
    return config;
  },
  (error) => {
    console.log("请求拦截器...失败1");
    return Promise.reject(error);
  }
);
axios.interceptors.request.use(
  (config) => {
    console.log("请求拦截器...成功2");
    config.timeout = 3000;
    return config;
  },
  (error) => {
    console.log("请求拦截器...失败2");
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  (response) => {
    console.log("响应拦截器...成功1");
    return response;
  },
  (error) => {
    console.log("响应拦截器...失败1");
    return Promise.reject(error);
  }
);
axios.interceptors.response.use(
  (response) => {
    console.log("响应拦截器...成功2");
    return response;
  },
  (error) => {
    console.log("响应拦截器...失败2");
    return Promise.reject(error);
  }
);

axios({}).then((value) => {
  console.log(value);
});
4. 模拟实现 Axios 取消请求功能
function Axios(config) {
  this.config = config;
}
// 原型request方法
Axios.prototype.request = function (config) {
  return dispatchRequest(config);
};
// dispatchRequest函数
function dispatchRequest(config) {
  return xhrAdapter(config);
}
// xhrAdapter
function xhrAdapter(config) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(config.method, config.url);
    xhr.send();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve({
            status: xhr.status,
            statusText: xhr.statusText,
          });
        } else {
          reject(new Error("请求失败,失败的状态码为" + xhr.status));
        }
      }
    };
    if (config.cancelToken) {
      config.cancelToken.promise.then((value) => {
        xhr.abort();
        console.log("请求取消执行");
      });
    }
  });
}
// 创建 axios 函数
let context = new Axios({});
let axios = Axios.prototype.request.bind(context);
// CancelToken 构造函数
function CancelToken(executor) {
  // 声明一个变量
  var resolvePromise;
  // 为实例对象添加属性
  this.promise = new Promise((resolve) => {
    resolvePromise = resolve;
  });
  // 调用 executor 函数
  executor(function () {
    // 执行 resolvePromise 函数,改变 promise 的状态
    resolvePromise();
  });
}
let cancel = null;
document.getElementById("send").onclick = function () {
  // 如果上一次的请求还没有结束,直接取消重新进行请求
  if (cancel) {
    cancel();
  }
  axios({
    method: "GET",
    url: "https://autumnfish.cn/search?keywords=我",
    cancelToken: new CancelToken(function (c) {
      cancel = c;
    }),
  }).then(
    (response) => {
      console.log(response);
      cancel = null;
    },
    (error) => {
      console.log(error);
    }
  );
};
document.getElementById("cancel").onclick = function () {
  cancel();
};
总结

axios 与 Axios 的关系

  • 从语法上来说:axios 不是 Axios 的实例
  • 从功能上来说:axios 是 Axios 的实例
  • axios 是 Axios.prototype.request 函数 bind() 返回的函数
  • axios 作为对象有 Axios 原型对象上的所有方法,有 Axios 对象上所有属性

instance 与 axios 的区别

  • 都是一个能发送任意请求的函数:request(config)
  • 都有发送特定请求的各种方法:get()/post()/put()/delete()
  • 都有默认配置和拦截器的属性:defaults/interceptors
  • 默认配置很可能不一样
  • instance 没有axios 后面添加的一些方法:create()/CancelToken()/all()

错误结果结构:

  • message
  • request
  • response
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐