axiosソースシリーズ(四)---Axiosとdispatch Requestとブロック

15862 ワード

前言
これは、多くの一般的な要求ライブラリです.プラットフォームにまたがって実現され、プロミセに戻ってチェーンで呼び出されます.ソースを完全に通過したら、自分の要求ライブラリに対する理解を高めることができます.
axiosソースシリーズ(一)---カタログ構造とツール関数axiosソースシリーズ(二)---アダプター内部axiosソースシリーズ(三)---標準設定とキャンセル要求axiosソースシリーズ(四)---AxiosとdispatchRequestとブロック
axios作成
これがAxiosライブラリの露出した使い方です.
axios/lib/axios.js
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);
axiosの例は、実際にAxiosコンストラクタによって作成され、プロトタイプのrequestのthisをaxiosに向けた例であり、その後、utils.extendの拡張関数を呼び出して、axios.prototypecontextをaxiosの例に追加します.
上に疑問があるかもしれない点はこの2点にあります.余計なことをしたように見えます.
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);

// Copy context to instance
utils.extend(instance, context);
しかし、ここのextendには単に拡張を行うだけでなく、結合this指向の機能も追加されていますので、2回の操作で目的が違っています.
  • instanceは拡張対象であり、Axiox.prototypeは拡張対象であり、contextは拡張対象functionであるthisはオブジェクト
  • を指定する.
  • instanceは依然として拡張対象であるが、今回のcontextは拡張対象
  • である.
    // Expose Axios class to allow class inheritance
    axios.Axios = Axios;
    
    // Factory for creating new instances
    axios.create = function create(instanceConfig) {
      return createInstance(mergeConfig(axios.defaults, instanceConfig));
    };
    内部がどのように配置されているか見てみます.
    axios/lib/core/mergConfig.js
    これは統合設定を担当する方法です.
    var utils = require('../utils');
    
    /**
     * Config-specific merge-function which creates a new config-object
     * by merging two configuration objects together.
     *
     * @param {Object} config1
     * @param {Object} config2
     * @returns {Object} New object resulting from merging config2 to config1
     */
    module.exports = function mergeConfig(config1, config2) {
      // eslint-disable-next-line no-param-reassign
      config2 = config2 || {};
      var config = {};
    
      var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
      var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
      var defaultToConfig2Keys = [
        'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',
        'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
        'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',
        'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',
        'httpsAgent', 'cancelToken', 'socketPath'
      ];
    
      utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
        if (typeof config2[prop] !== 'undefined') {
          config[prop] = config2[prop];
        }
      });
    
      utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {
        if (utils.isObject(config2[prop])) {
          config[prop] = utils.deepMerge(config1[prop], config2[prop]);
        } else if (typeof config2[prop] !== 'undefined') {
          config[prop] = config2[prop];
        } else if (utils.isObject(config1[prop])) {
          config[prop] = utils.deepMerge(config1[prop]);
        } else if (typeof config1[prop] !== 'undefined') {
          config[prop] = config1[prop];
        }
      });
    
      utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
        if (typeof config2[prop] !== 'undefined') {
          config[prop] = config2[prop];
        } else if (typeof config1[prop] !== 'undefined') {
          config[prop] = config1[prop];
        }
      });
    
      var axiosKeys = valueFromConfig2Keys
        .concat(mergeDeepPropertiesKeys)
        .concat(defaultToConfig2Keys);
    
      var otherKeys = Object
        .keys(config2)
        .filter(function filterAxiosKeys(key) {
          return axiosKeys.indexOf(key) === -1;
        });
    
      utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {
        if (typeof config2[prop] !== 'undefined') {
          config[prop] = config2[prop];
        } else if (typeof config1[prop] !== 'undefined') {
          config[prop] = config1[prop];
        }
      });
    
      return config;
    };
    呼び出しの方式から入手できます.config 1はaxiosのデフォルト設定で、config 2はユーザがカスタマイズした構成です.
    中にはデフォルトで3つの優先度の設定があります.
  • valueFroom Config 2 Keys:config 2内の関連属性がundefinedでないならconfig 2の値を指定します.ここではconfig 1の構成を考慮しません.
  • mergDeepPropertiesKeys:深層要求ヘッド構造、config 2優先度はconfig 1より高く、まず対象構造を処理してから他の非undefinedタイプを処理する
  • default ToConfig 2 Keys:直接に値を割り当てることができる要求ヘッダのリスト、config 2優先度はconfig 1
  • より高いです.
    上記の3つの構成をクリアしたら、私達は得ることができます.
  • axiosKeys:config 1とconfig 2は、上の3つのデフォルトの構成を組み合わせたマッピング配列
  • を含んでいます.
  • otherKeys:config 2にaxiosKeysに含まれていない構成属性をフィルタリングします.基本的には非常用またはカスタムフィールドです.config 1を考慮しない場合も
  • です.
    最後に、others Keysに関する構成も最終結果にまとめて返します.
    // Expose Cancel & CancelToken
    axios.Cancel = require('./cancel/Cancel');
    axios.CancelToken = require('./cancel/CancelToken');
    axios.isCancel = require('./cancel/isCancel');
    
    // Expose all/spread
    axios.all = function all(promises) {
      return Promise.all(promises);
    };
    axios.spread = require('./helpers/spread');
    
    module.exports = axios;
    
    // Allow use of default import syntax in TypeScript
    module.exports.default = axios;
    axiosの例に方法を追加します.まずspreadがどのような効果があるかを見ます.
    axios/lib/helpers/spread.js
    /**
     * Syntactic sugar for invoking a function and expanding an array for arguments.
     *
     * Common use case would be to use `Function.prototype.apply`.
     *
     *  ```js
     *  function f(x, y, z) {}
     *  var args = [1, 2, 3];
     *  f.apply(null, args);
     *  ```
     *
     * With `spread` this example can be re-written.
     *
     *  ```js
     *  spread(function(x, y, z) {})([1, 2, 3]);
     *  ```
     *
     * @param {Function} callback
     * @returns {Function}
     */
    module.exports = function spread(callback) {
      return function wrap(arr) {
        return callback.apply(null, arr);
      };
    };
    ただの文法飴です.黒魔法はありません.
    Axiosコア構造関数
    axios/lib/core/Axios.js
    これはAxiosの構造関数です.
    var utils = require('./../utils');
    var buildURL = require('../helpers/buildURL');
    var InterceptorManager = require('./InterceptorManager');
    var dispatchRequest = require('./dispatchRequest');
    var mergeConfig = require('./mergeConfig');
    
    /**
     * Create a new instance of Axios
     *
     * @param {Object} instanceConfig The default config for the instance
     */
    function Axios(instanceConfig) {
      this.defaults = instanceConfig;
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
      };
    }
    Axiosのインスタンスを作成すると、デフォルトでは設定が保存されます.内部ブロックはそれぞれ要求と応答に与えられます.
    /**
     * Dispatch a request
     *
     * @param {Object} config The config specific for this request (merged with this.defaults)
     */
    Axios.prototype.request = function request(config) {
      /*eslint no-param-reassign:0*/
      // Allow for axios('example/url'[, config]) a la fetch API
      if (typeof config === 'string') {
        config = arguments[1] || {};
        config.url = arguments[0];
      } else {
        config = config || {};
      }
    
      config = mergeConfig(this.defaults, config);
    
      // Set config.method
      if (config.method) {
        config.method = config.method.toLowerCase();
      } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
      } else {
        config.method = 'get';
      }
    
      // Hook up interceptors middleware
      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は二つの部分のロジックを分けます.
  • は、最初のパラメータ着信要求アドレスの呼び出し方式をサポートし、デフォルト構成と着信構成
  • を統合する.
  • chainはフックの中間部品としてチェーンコールを行い、最終的に
    [
        this.interceptors.request.fulfilled, this.interceptors.request.rejected,//     
        dispatchRequest, undefined,//     
        this.interceptors.response.fulfilled, this.interceptors.response.rejected,//     
    ]
  • になります.
  • は、whileのチェーンを使用してchainの関数を呼び出し、開始から受信までの完全なフロー
  • を完了する.
    Axios.prototype.getUri = function getUri(config) {
      //     
      config = mergeConfig(this.defaults, config);
      //     URL
      return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
    };
    
    // Provide aliases for supported request methods
    utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
      /*eslint func-names:0*/
      Axios.prototype[method] = function(url, config) {
        return this.request(utils.merge(config || {}, {
          method: method,
          url: url
        }));
      };
    });
    
    utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
      /*eslint func-names:0*/
      Axios.prototype[method] = function(url, data, config) {
        return this.request(utils.merge(config || {}, {
          method: method,
          url: url,
          data: data
        }));
      };
    });
    
    module.exports = Axios;
    すべての要求方式を巡回して、内蔵のrequest方法を呼び出します.
    迎撃器
    axios/lib/core/InterceptorManager.js
    スクリーンショットの構造関数を定義します.
    var utils = require('./../utils');
    
    function InterceptorManager() {
      this.handlers = [];
    }
    
    /**
     * Add a new interceptor to the stack
     *
     * @param {Function} fulfilled The function to handle `then` for a `Promise`
     * @param {Function} rejected The function to handle `reject` for a `Promise`
     *
     * @return {Number} An ID used to remove interceptor later
     */
    InterceptorManager.prototype.use = function use(fulfilled, rejected) {
      this.handlers.push({
        fulfilled: fulfilled,
        rejected: rejected
      });
      return this.handlers.length - 1;
    };
    
    /**
     * Remove an interceptor from the stack
     *
     * @param {Number} id The ID that was returned by `use`
     */
    InterceptorManager.prototype.eject = function eject(id) {
      if (this.handlers[id]) {
        this.handlers[id] = null;
      }
    };
    
    /**
     * Iterate over all the registered interceptors
     *
     * This method is particularly useful for skipping over any
     * interceptors that may have become `null` calling `eject`.
     *
     * @param {Function} fn The function to call for each interceptor
     */
    InterceptorManager.prototype.forEach = function forEach(fn) {
      utils.forEach(this.handlers, function forEachHandler(h) {
        if (h !== null) {
          fn(h);
        }
      });
    };
    
    module.exports = InterceptorManager;
    関数全体は実はいくつかのことをしました.
  • は、handlersキュー
  • を内蔵した例を生成する.
  • プロトタイプバインディングuseによって登録された方法で、キューは、適合及び失敗関数方法を含むブロック対象に入り、該オブジェクトのキュー内のid(実際にはインデックス値)
  • に戻る.
  • プロトタイプバインディングejectは、指定されたidのブロッキングオブジェクト
  • を削除する.
  • プロトタイプバインディングforEachは、登録されたすべてのスクリーンセーバ
  • を巡回する方法である.
    スクリーンショットの例
    //        
    const reqInterceptor = axios.interceptors.request.use(function (config) {
        //            
        return config;
      }, function (error) {
        //          
        return Promise.reject(error);
      });
    
    //        
    const resInterceptor = axios.interceptors.response.use(function (response) {
        //          
        return response;
      }, function (error) {
        //          
        return Promise.reject(error);
      });
      
    //   
    axios.interceptors.request.eject(reqInterceptor);
    axios.interceptors.request.eject(resInterceptor);
    スケジュール要求
    axios/lib/core/dispatchRequest.js
    これはスケジューリング要求を担当する関数で、Promiseに戻ります.
    var utils = require('./../utils');
    var transformData = require('./transformData');
    var isCancel = require('../cancel/isCancel');
    var defaults = require('../defaults');
    
    /**
     * Throws a `Cancel` if cancellation has been requested.
     */
    function throwIfCancellationRequested(config) {
      if (config.cancelToken) {
        config.cancelToken.throwIfRequested();
      }
    }
    
    /**
     * Dispatch a request to the server using the configured adapter.
     *
     * @param {object} config The config that is to be used for the request
     * @returns {Promise} The Promise to be fulfilled
     */
    module.exports = function dispatchRequest(config) {
      throwIfCancellationRequested(config);
    
      // Ensure headers exist
      config.headers = config.headers || {};
    
      // Transform request data
      config.data = transformData(
        config.data,
        config.headers,
        config.transformRequest
      );
    
      // Flatten headers
      config.headers = utils.merge(
        config.headers.common || {},
        config.headers[config.method] || {},
        config.headers || {}
      );
    
      utils.forEach(
        ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
        function cleanHeaderConfig(method) {
          delete config.headers[method];
        }
      );
    
      var adapter = config.adapter || defaults.adapter;
    
      return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);
    
        // Transform response data
        response.data = transformData(
          response.data,
          response.headers,
          config.transformResponse
        );
    
        return response;
      }, function onAdapterRejection(reason) {
        if (!isCancel(reason)) {
          throwIfCancellationRequested(config);
    
          // Transform response data
          if (reason && reason.response) {
            reason.response.data = transformData(
              reason.response.data,
              reason.response.headers,
              config.transformResponse
            );
          }
        }
    
        return Promise.reject(reason);
      });
    };
    throwIfCancellationRequestedは、キャンセルされた要求があれば、エラーをスローしたということです.dispatchRequestは、構成されたadapterを使用して、サービス端末に要求を開始し、いくつかのステップに分けられる.
  • は、要求がキャンセルされたらエラーが発生したことを検出した.
  • は、要求ヘッドが
  • に存在することを確認する.
  • は、データ及び要求ヘッダに着信し、データタイプ変換要求データ
  • は、要求ヘッダのcommonと、現在の要求のmethodsとを第1層構造
  • にマージする.
  • header属性の不要な属性を削除する
  • .
  • 確認adapter
  • は、adapter(config)を実行してPromiseに戻り、対応する動作を実行する.
  • 成功
  • は、要求がキャンセルされたらエラーが発生したことを検出した.
  • そうでなければ、処理はデータフォーマット
  • に戻る.
  • 失敗しました
  • !isCancel(reason)が成立すれば、要求がキャンセルされた場合にエラーが発生したことが検出される.
  • そうでなければ、直接データ
  • に戻ります.
    axios/lib/core/transformData.js
    解析要求/応答データの担当は、単にutils.forEachを使用しているだけです.
    var utils = require('./../utils');
    
    /**
     * Transform the data for a request or a response
     *
     * @param {Object|String} data The data to be transformed
     * @param {Array} headers The headers for the request or response
     * @param {Array|Function} fns A single function or Array of functions
     * @returns {*} The resulting transformed data
     */
    module.exports = function transformData(data, headers, fns) {
      /*eslint no-param-reassign:0*/
      utils.forEach(fns, function transform(fn) {
        data = fn(data, headers);
      });
    
      return data;
    };
    これでaxiosライブラリ全体のソースコードはすでに説明済みです.