乐趣区

关于前端:转-axios-是如何封装-HTTP-请求的

转自掘金:zhangbao90s
原载于 TutorialDocs 网站的文章《How to Implement an HTTP Request Library with Axios》。

概述

前端开发中,常常会遇到发送异步申请的场景。一个功能齐全的 HTTP 申请库能够大大降低咱们的开发成本,进步开发效率。

axios 就是这样一个 HTTP 申请库,近年来十分热门。目前,它在 GitHub 上领有超过 40,000 的 Star,许多权威人士都举荐应用它。

因而,咱们有必要理解下 axios 是如何设计,以及如何实现 HTTP 申请库封装的。撰写本文时,axios 以后版本为 0.18.0,咱们以该版本为例,来浏览和剖析局部外围源代码。axios 的所有源文件都位于 lib 文件夹中,下文中提到的门路都是绝对于 lib 来说的。

本文咱们次要探讨:

  • 怎么应用 axios。
  • axios 的外围模块(申请、拦截器、撤销)是如何设计和实现的?
  • axios 的设计长处是什么?

如何应用 axios

要了解 axios 的设计,首先须要看一下如何应用 axios。咱们举一个简略的例子来阐明下 axios API 的应用。

发送申请

axios({
  method: 'get',
  url: 'http://bit.ly/2mTM3nY',
  responseType: 'stream',
}).then(function (response) {response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
})

这是一个官网示例。从下面的代码中能够看到,axios 的用法与 jQuery 的 ajax 办法十分相似,两者都返回一个 Promise 对象(在这里也能够应用胜利回调函数,但还是更举荐应用 Promiseawait),而后再进行后续操作。

这个实例很简略,不须要我解释了。咱们再来看看如何增加一个拦截器函数。

增加拦截器函数

// 增加一个申请拦截器。留神,这外面有 2 个函数——别离是胜利和失败时的回调函数,这样设计的起因会在之后介绍
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)
  }
)

从下面的代码,咱们能够晓得:发送申请之前,咱们能够对申请的配置参数(config)做解决;在申请失去响应之后,咱们能够对返回数据做解决。当申请或响应失败时,咱们还能指定对应的谬误处理函数。

撤销 HTTP 申请

在开发与搜寻相干的模块时,咱们常常要频繁地发送数据查问申请。一般来说,当咱们发送下一个申请时,须要撤销上个申请。因而,能撤销相干申请性能十分有用。axios 撤销申请的示例代码如下:

const CancelToken = axios.CancelToken
const source = CancelToken.source()

// 例子一
axios
  .get('/user/12345', {cancelToken: source.token,})
  .catch(function (thrown) {if (axios.isCancel(thrown)) {console.log('申请撤销了', thrown.message)
    } else {// 处理错误}
  })

// 例子二
axios
  .post(
    '/user/12345',
    {name: '新名字',},
    {cancelToken: source.token,}
  )
  // 撤销申请 (信息参数是可选的)
  .source.cancel('用户撤销了申请')

从上例中能够看到,在 axios 中,应用基于 CancelToken 的撤销申请计划。然而,该提案现已撤回,详情如 点这里。具体的撤销申请的实现办法,将在前面的源代码剖析的中解释。

axios 外围模块的设计和实现

通过下面的例子,我置信每个人都对 axios 的应用有一个大抵的理解了。上面,咱们将依据模块剖析 axios 的设计和实现。上面的图片,是我在本文中会介绍到的源代码文件。如果您感兴趣,最好在浏览时克隆相干的代码,这能加深你对相干模块的了解。

HTTP 申请模块

申请模块的代码放在了 core/dispatchRequest.js 文件中,这里我只展现了一些要害代码来简略阐明:


module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);

    // 其余源码
    // 默认适配器是一个模块,能够依据以后环境抉择应用 Node 或者 XHR 发送申请。var adapter = config.adapter || defaults.adapter;

    return adapter(config).then(function onAdapterResolution(response) {throwIfCancellationRequested(config);

        // 其余源码

        return response;
    }, function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);

            // 其余源码

            return Promise.reject(reason);
        });

};

下面的代码中,咱们可能晓得 dispatchRequest 办法是通过 config.adapter,取得发送申请模块的。咱们还能够通过传递,符合规范的适配器函数来代替原来的模块(一般来说,咱们不会这样做,但它是一个涣散耦合的扩大点)。

defaults.js 文件中,咱们能够看到相干适配器的抉择逻辑——依据以后容器的一些独特属性和构造函数,来确定应用哪个适配器。

function getDefaultAdapter() {
  var adapter
  // 只有在 Node.js 中蕴含 process 类型对象时,才应用它的申请模块
  if (
    typeof process !== 'undefined' &&
    Object.prototype.toString.call(process) === '[object process]'
  ) {
    // Node.js 申请模块
    adapter = require('./adapters/http')
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // 浏览器申请模块
    adapter = require('./adapters/xhr')
  }
  return adapter
}

axios 中的 XHR 模块绝对简略,它是对 XMLHTTPRequest 对象的封装,这里我就不再解释了。有趣味的同学,能够本人浏览源源码看看,源码位于 adapters/xhr.js 文件中。

拦截器模块

当初让咱们看看 axios 是如何解决,申请和响应拦截器函数的。这就波及到了 axios 中的对立接口 ——request 函数。

Axios.prototype.request = function request(config) {
  // 其余源码

  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
}

这个函数是 axios 发送申请的接口。因为函数实现代码相当长,这里我会简略地探讨相干设计思维:

chain 是一个执行队列。队列的初始值是一个携带配置(config)参数的 Promise 对象。

在执行队列中,初始函数 dispatchRequest 用来发送申请,为了与 dispatchRequest 对应,咱们增加了一个 undefined。增加 undefined 的起因是须要给 Promise 提供胜利和失败的回调函数,从上面代码里的 promise = promise.then(chain.shift(), chain.shift()); 咱们就能看进去。因而,函数 dispatchRequestundefiend 能够看成是一对函数。

在执行队列 chain 中,发送申请的 dispatchReqeust 函数处于两头地位。它后面是申请拦截器,应用 unshift 办法插入;它前面是响应拦截器,应用 push 办法插入,在 dispatchRequest 之后。须要留神的是,这些函数都是成对的,也就是一次会插入两个。

浏览下面的 request 函数代码,咱们大抵晓得了怎么应用拦截器。下一步,来看看怎么撤销一个 HTTP 申请。

撤销申请模块

与撤销申请相干的模块位于 Cancel/ 文件夹下,当初咱们来看下相干外围代码。

首先,咱们来看下根底 Cancel 类。它是一个用来记录撤销状态的类,具体代码如下:

function Cancel(message) {this.message = message}

Cancel.prototype.toString = function toString() {return 'Cancel' + (this.message ? ':' + this.message : '')
}

Cancel.prototype.__CANCEL__ = true

应用 CancelToken 类时,须要向它传递一个 Promise 办法,用来实现 HTTP 申请的撤销,具体代码如下:

function CancelToken(executor) {if (typeof executor !== 'function') {throw new TypeError('executor must be a function.')
  }

  var resolvePromise
  this.promise = new Promise(function promiseExecutor(resolve) {resolvePromise = resolve})

  var token = this
  executor(function cancel(message) {if (token.reason) {
      // 曾经被撤销了
      return
    }

    token.reason = new Cancel(message)
    resolvePromise(token.reason)
  })
}

CancelToken.source = function source() {
  var cancel
  var token = new CancelToken(function executor(c) {cancel = c})
  return {
    token: token,
    cancel: cancel,
  }
}

adapters/xhr.js 文件中,撤销申请的中央是这样写的:

if (config.cancelToken) {
  // 期待撤销
  config.cancelToken.promise.then(function onCanceled(cancel) {if (!request) {return}

    request.abort()
    reject(cancel)
    // 重置申请
    request = null
  })
}

通过下面的撤销 HTTP 申请的例子,让咱们简要地讨论一下相干的实现逻辑:

在须要撤销的申请中,调用 CancelToken 类的 source 办法类进行初始化,会失去一个蕴含 CancelToken 类实例 A 和 cancel 办法的对象。

当 source 办法正在返回实例 A 的时候,一个处于 pending 状态的 promise 对象初始化实现。在将实例 A 传递给 axios 之后,promise 就能够作为撤销申请的触发器应用了。

当调用通过 source 办法返回的 cancel 办法后,实例 A 中 promise 状态从 pending 变成 fulfilled,而后立刻触发 then 回调函数。于是 axios 的撤销办法——request.abort() 被触发了。

axios 这样设计的益处是什么?

发送申请函数的解决逻辑

如前几章所述,axios 不将用来发送申请的 dispatchRequest 函数看做一个非凡函数。实际上,dispatchRequest 会被放在队列的两头地位,以便保障队列解决的一致性和代码的可读性。

适配器的解决逻辑

在适配器的解决逻辑上,http 和 xhr 模块(一个是在 Node.js 中用来发送申请的,一个是在浏览器里用来发送申请的)并没有在 dispatchRequest 函数中应用,而是各自作为独自的模块,默认通过 defaults.js 文件中的配置办法引入的。因而,它不仅确保了两个模块之间的低耦合,而且还为未来的用户提供了定制申请发送模块的空间。

撤销 HTTP 申请的逻辑

在撤销 HTTP 申请的逻辑中,axios 设计应用 Promise 来作为触发器,将 resolve 函数裸露在里面,并在回调函数里应用。它不仅确保了外部逻辑的一致性,而且还确保了在须要撤销申请时,不须要间接更改相干类的样例数据,以防止在很大水平上入侵其余模块。

总结

本文具体介绍了 axios 的用法、设计思维和实现办法。在浏览之后,您能够理解 axios 的设计,并理解模块的封装和交互。

本文只介绍了 axios 的外围模块,如果你对其余模块代码感兴趣,能够到 GitHub 上查看。

(完)

退出移动版