2

Ajax Fetch 和 Axios(持续更新中...)

知识点梳理

  1. AJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。
  2. JavaScript 代码都是单线程执行的,由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,回调函数不好看,不利于代码复用,而链式写法好处 逻辑统一、利于复用,所以出现 Primose Promise 有各种开源实现,在 ES6 中被统一规范,由浏览器直接支持。
  3. async await 是 Promise 语法糖,使异步的逻辑书写标准的同步函数
  4. Fetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。
  5. axios 是基于 Promise 的浏览器和 node.js http 客户端
  6. Generator 是另外一种全新的 javascript 异步解决方案,之后再说明

1. 原生 XHR

Ajax

AJAX 不是 JavaScript 的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用 JavaScript 执行异步网络请求。
属性 描述
onreadystatechange 每当 readyState 属性改变时,就会调用该函数。
readyState 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
0: 请求未初始化
1: 服务器连接已建立,open()方法已调用,但是 send()方法未调用
2: 请求已连接 send()方法已调用,HTTP 请求已发送到 Web 服务器。未接收到相应
3: 请求处理中
4: 请求已完成,且响应已就绪
status 200: "OK"
404: 未找到页面
;(function() {
  var xmlhttp
  if (window.XMLHttpRequest) {
    //  IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
    xmlhttp = new XMLHttpRequest()
  } else {
    // IE6, IE5 浏览器执行代码
    xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
  }
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      console.log(xmlhttp.responseText)
    }
  }
  xmlhttp.open('GET', 'http://www.runoob.com/try/ajax/ajax_info.txt', true)
  xmlhttp.send()
})()

Ajax 安全限制

浏览器的同源策略导致的。默认情况下,JavaScript 在发送 AJAX 请求时,URL 的域名必须和当前页面完全一致

跨域请求

  1. 通过 Flash 插件发送 HTTP 请求,这种方式可以绕过浏览器的安全限制,但必须安装 Flash,并且跟 Flash 交互。不过 Flash 用起来麻烦,而且现在用得也越来越少了。
  2. 通过在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器
  3. JSONP 它有个限制,只能用 GET 请求,并且要求返回 JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用 JavaScript 资源
  4. CORS CORS 全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。面这种跨域请求,称之为“简单请求”。简单请求包括 GET、HEAD 和 POST(POST 的 Content-Type 类型仅限 application/x-www-form-urlencoded、multipart/form-data 和 text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足 90%的需求

2.Promise

在 JavaScript 的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现

function callback() {
  console.log('Done')
}
console.log('before setTimeout()')
setTimeout(callback, 1000) // 1秒钟后调用callback函数
console.log('after setTimeout()')

链式写法的好处在于,先统一执行 AJAX 逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用 success 函数或 fail 函数。

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在 JavaScript 中称为 Promise 对象。

使用 Promise 封装 ajax 简化异步处理

// ajax函数将返回Promise对象:
function ajax(method, url, data) {
  var request = new XMLHttpRequest()
  return new Promise(function(resolve, reject) {
    request.onreadystatechange = function() {
      if (request.readyState === 4) {
        if (request.status === 200) {
          resolve(request.responseText)
        } else {
          reject(request.status)
        }
      }
    }
    request.open(method, url)
    request.send(data)
  })
}

var p = ajax('GET', '/api/categories')
p.then(function(text) {
  // 如果AJAX成功,获得响应内容
}).catch(function(status) {
  // 如果AJAX失败,获得响应代码
})

Promise 使用方法

;(function() {
  console.time('doIt')
  const time1 = 300
  step1(time1)
    .then(time2 => step2(time2))
    .then(time3 => step3(time3))
    .then(result => {
      console.log(`result is ${result}`)
      console.timeEnd('doIt')
    })
})()

function takeLongTime(n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 200), n)
  })
}
function step1(n) {
  console.log(`step1 with ${n}`)
  return takeLongTime(n)
}
function step2(n) {
  console.log(`step2 with ${n}`)
  return takeLongTime(n)
}
function step3(n) {
  console.log(`step3 with ${n}`)
  return takeLongTime(n)
}

Promise.all()

两个任务是可以并行执行的,用 Promise.all()实现
var p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 500, 'P1')
})
var p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 6000, 'P2')
})
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function(results) {
  console.log(results) // 获得一个Array: ['P1', 'P2']
})

Promise.race()

有些时候,多个异步任务是为了容错,只需要获得先返回的结果即可
var p1 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 3000, 'P1')
})
var p2 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 6000, 'P2')
})
// 同时执行p1和p2,其中一个完成后执行then:
Promise.race([p1, p2]).then(function(results) {
  console.log(results) //  // 'P1'
})

3.async await

await

await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用

await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function。
若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。

另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。

// 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  })
}

async function f1() {
  var x = await resolveAfter2Seconds(10)
  console.log(x) // 10
}
f1()
// 如果 Promise 处理异常,则异常值被抛出。
async function f3() {
  try {
    var z = await Promise.reject(30)
  } catch (e) {
    console.log(e) // 30
  }
}
f3()

async

async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。

一个返回的 Promise 对象会以 async function 的返回值进行解析(resolved),或者以该函数抛出的异常进行回绝(rejected)。

async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复 async 函数的执行并返回解析值(resolved)。

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved')
    }, 2000)
  })
}

async function asyncCall() {
  console.log('calling')
  var result = await resolveAfter2Seconds()
  console.log(result)
  // expected output: 'resolved'
}

asyncCall()

4.Fetch

Fetch 是一个现代的概念, 等同于 XMLHttpRequest。它提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。

Fetch 是挂在在 window 下的

Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。Fetch 还利用到了请求的异步特性——它是基于 Promise 的。

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json()
  })
  .then(function(myJson) {
    console.log(myJson)
  })
postData('http://example.com/answer', { answer: 42 })
  .then(data => console.log(data)) // JSON from `response.json()` call
  .catch(error => console.error(error))

function postData(url, data) {
  // Default options are marked with *
  return fetch(url, {
    body: JSON.stringify(data), // must match 'Content-Type' header
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, same-origin, *omit
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    },
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, cors, *same-origin
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer' // *client, no-referrer
  }).then(response => response.json()) // parses response to JSON
}

5. axios

axios 是一个基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端
axios
  .get('/user', { params: { ID: 12345 } })
  .then(function(response) {})
  .catch(function(error) {})

可以通过将相关配置传递给 axios 来进行请求。

axios({
  method: 'post',
  url: '/user/12345',
  data: { firstName: 'Fred', lastName: 'Flintstone' }
})

请求方法别名

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]])

Request Config 请求设置

{
    //`url`是服务器链接,用来请求
    url:'/user',

    //`method`是发起请求时的请求方法
    method:`get`,

    //`baseURL`如果`url`不是绝对地址,那么将会加在其前面。
    //当axios使用相对地址时这个设置非常方便
    //在其实例中的方法
    baseURL:'http://some-domain.com/api/',

    //`transformRequest`允许请求的数据在传到服务器之前进行转化。
    //这个只适用于`PUT`,`GET`,`PATCH`方法。
    //数组中的最后一个函数必须返回一个字符串或者一个`ArrayBuffer`,或者`Stream`,`Buffer`实例,`ArrayBuffer`,`FormData`
    transformRequest:[function(data){
        //依自己的需求对请求数据进行处理
        return data;
    }],

    //`transformResponse`允许返回的数据传入then/catch之前进行处理
    transformResponse:[function(data){
        //依需要对数据进行处理
        return data;
    }],

    //`headers`是自定义的要被发送的头信息
    headers:{'X-Requested-with':'XMLHttpRequest'},

    //`params`是请求连接中的请求参数,必须是一个纯对象,或者URLSearchParams对象
    params:{
        ID:12345
    },

    //`paramsSerializer`是一个可选的函数,是用来序列化参数
    //例如:(https://ww.npmjs.com/package/qs,http://api.jquery.com/jquery.param/)
    paramsSerializer: function(params){
        return Qs.stringify(params,{arrayFormat:'brackets'})
    },

    //`data`是请求体需要设置的数据
    //只适用于应用的'PUT','POST','PATCH',请求方法
    //当没有设置`transformRequest`时,必须是以下其中之一的类型(不可重复?):
    //-string,plain object,ArrayBuffer,ArrayBufferView,URLSearchParams
    //-仅浏览器:FormData,File,Blob
    //-仅Node:Stream
    data:{
        firstName:'fred'
    },
    //`timeout`定义请求的时间,单位是毫秒。
    //如果请求的时间超过这个设定时间,请求将会停止。
    timeout:1000,

    //`withCredentials`表明是否跨网站访问协议,
    //应该使用证书
    withCredentials:false //默认值

    //`adapter`适配器,允许自定义处理请求,这会使测试更简单。
    //返回一个promise,并且提供验证返回(查看[response docs](#response-api))
    adapter:function(config){
        /*...*/
    },

    //`auth`表明HTTP基础的认证应该被使用,并且提供证书。
    //这个会设置一个`authorization` 头(header),并且覆盖你在header设置的Authorization头信息。
    auth:{
        username:'janedoe',
        password:'s00pers3cret'
    },

    //`responsetype`表明服务器返回的数据类型,这些类型的设置应该是
    //'arraybuffer','blob','document','json','text',stream'
    responsetype:'json',

    //`xsrfHeaderName` 是http头(header)的名字,并且该头携带xsrf的值
    xrsfHeadername:'X-XSRF-TOKEN',//默认值

    //`onUploadProgress`允许处理上传过程的事件
    onUploadProgress: function(progressEvent){
        //本地过程事件发生时想做的事
    },

    //`onDownloadProgress`允许处理下载过程的事件
    onDownloadProgress: function(progressEvent){
        //下载过程中想做的事
    },

    //`maxContentLength` 定义http返回内容的最大容量
    maxContentLength: 2000,

    //`validateStatus` 定义promise的resolve和reject。
    //http返回状态码,如果`validateStatus`返回true(或者设置成null/undefined),promise将会接受;其他的promise将会拒绝。
    validateStatus: function(status){
        return status >= 200 && stauts < 300;//默认
    },

    //`httpAgent` 和 `httpsAgent`当产生一个http或者https请求时分别定义一个自定义的代理,在nodejs中。
    //这个允许设置一些选选个,像是`keepAlive`--这个在默认中是没有开启的。
    httpAgent: new http.Agent({keepAlive:treu}),
    httpsAgent: new https.Agent({keepAlive:true}),

    //`proxy`定义服务器的主机名字和端口号。
    //`auth`表明HTTP基本认证应该跟`proxy`相连接,并且提供证书。
    //这个将设置一个'Proxy-Authorization'头(header),覆盖原先自定义的。
    proxy:{
        host:127.0.0.1,
        port:9000,
        auth:{
            username:'cdd',
            password:'123456'
        }
    },

    //`cancelTaken` 定义一个取消,能够用来取消请求
    //(查看 下面的Cancellation 的详细部分)
    cancelToken: new CancelToken(function(cancel){
    })
}

返回响应概要 Response Schema

{
    //`data`是服务器的提供的回复(相对于请求)
    data{},

    //`status`是服务器返回的http状态码
    status:200,


    //`statusText`是服务器返回的http状态信息
    statusText: 'ok',

    //`headers`是服务器返回中携带的headers
    headers:{},

    //`config`是对axios进行的设置,目的是为了请求(request)
    config:{}
}

全局默认设置

axios.defaults.baseURL = 'https://api.example.com'
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN
axios.defaults.headers.post['Content-Type'] =
  'application/x-www-form-urlencoded'
拦截器 interceptors

你可以在请求或者返回被 then 或者 catch 处理之前对他们进行拦截。

//添加一个请求拦截器
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)
  }
)

fetch axios封装

项目中使用fetch 和axios 时,常常会做一些底层的封装,比如一些统一的参数处理,异常处理,HTTP >= 300的处理
下一遍将尝试对axios、fetch的封装

参考

  1. AJAX
  2. Promise
  3. async await 语法描述
  4. Fetch 语法描述
  5. ECMAScript 6 入门
  6. 翻译比较到位的 axios 中文文档

缓缓
1.3k 声望62 粉丝