阅读axios源码

axios调用方法

  1. axios.create(config)
  2. axios.method(url, config)

实现

function createInstance(defaultConfig) {
  const context = new Axios(defaultConfig)
  const instance = Axios.prototype.request.bind(context)
  
  // 实现调用方法二
  // 将Axios.prototype方法都复制给instance,context作为this
  utils.extend(instance, Axios.prototype, context)
  // 将context的所有实例属性也复制给instance
  utils.extend(instance, context, null)
  
  // 实现调用方法一
  instance.create = function create(config) {
    return createInstance(mergeConfig(default, config))
  }
  
  return instance
}
const axios = createInstance(defaultConfig)

defaults中有什么

const defaults = {
  ...,
  headers: {
    common: {
      'Accept': 'application/json, text/plain, */*',
      'Content-Type': undefined
    }
  }
}

utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], (method) => {
  defaults.headers[method] = {};
});
  • 由此可见,defaults会为headers中添加common以及其他请求方法为属性名,值为对象

Axios类

class Axios {
  constructor(instanceConfig) {
    this.defaults = instanceConfig
    this.interceptors = {
      request: new InterceptorManager()
      response: new InterceptorManager(),
    }
  }
  request(configOrUrl, config) {
    if (typeof configOrUrl === 'string') {
      config = config || {}
      config.url = configOrUrl
    } else {
      config = configOrUrl || {}
    }
    
    config = mergeConfig(this.defaults, config)
    config.method = (config.method || thhis.default.method || 'get').toLowerCase()
    
    const { headers } = config;
    let contextHeaders = headers && utils.merge(
      headers.common,
      headers[config.method]
    );
    // 删除冗余
    headers && utils.forEach(
      ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
      (method) => {
        delete headers[method];
      }
    );
    config.headers = AxiosHeaders.concat(contextHeaders, headers);
    
    const requestInterceptorChain = [];
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      ...
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    const responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });
    
    const chain = [dispatchRequest.bind(this), undefined];
    chain.unshift.apply(chain, requestInterceptorChain);
    chain.push.apply(chain, responseInterceptorChain);
    len = chain.length;

    promise = Promise.resolve(config);

    while (i < len) {
      promise = promise.then(chain[i++], chain[i++]);
    }

    return promise;
  }
}
  • 这地方主要是处理了config、URL以及header,然后将请求拦截器逆序加入数组,响应拦截器顺序加入数组,当一个请求发送出去的时候为 请求拦截器逆序执行 -> 发送请求 -> 响应拦截器顺序执行

拦截器

class InterceptorManager {
  constructor() {
    this.handlers = []
  }
  use(fulfilled, rejected, options) {
    this.handlers.push({
      fulfilled,
      rejected,
      synchronous: options ? options.synchronous : false,
      runWhen: options ? options.runWhen : null
    })
    return this.handlers.length - 1
  }
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }
  clear() {
    if (this.handlers) {
      this.handlers = [];
    }
  }
}
  • 拦截器管理就是一个数组,使用use存放成功和失败对应的回调,会返回一个id也就是当前存放的下标,用于取消存放

发送请求的dispatchRequest

function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new CanceledError(null, config);
  }
}

function dispatchRequest(config) {
  throwIfCancellationRequested(config); // 判断是否终止了请求
  const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
  
  return adapter(config).then(response => {
    throwIfCancellationRequested(config); // 即使成功了也需要判断在发送请求是否终止了
    ...
    return response
  }, reason => {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);
    }
    return Promise.reject(reason)
  })
}

真正发送请求的adapter

  • Axios 是一个基于 promise 网络请求库,作用于node和浏览器中。 在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests
const knownAdapters = {
http: httpAdapter,
xhr: xhrAdapter
}
// xhrAdapter
function dispatchXhrRequest(config) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
const fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
request.timeout = config.timeout;
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
// Clean up request
request = null;
};
request.onerror = function handleError() {
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
// Clean up request
request = null;
};
request.ontimeout = function handleTimeout() {
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
const transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
request));
// Clean up request
request = null;
};
let onCanceled;
if (config.cancelToken || config.signal) {
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled); // 调用cancelToken.cancel的时候会去调用订阅的方法,从而可以取消promise
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
function done() { // 执行一些清除操作
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
request.onloadend = onloadend;
request.send(requestData || null);
})
}
  • 总的来说就是根据环境判断可以用node的http还是浏览器的xhr,其中对config传入的cancelToken调用其订阅方法,订阅可以取消promise的方法,当取消的时候会调用request.abort()从而reject掉promise,即使发送了网络请求,在后续adapter根据promise对应的resolve或reject状态会进行判断是否取消过,取消过则丢出错误。

取消请求的CancelToken

class CancelToken {
static source() {
let cancel
const token = new CancelToken(c => {
cancel = c
})
return {
token,
cancel
}
}
constructor(executor) {
const token = this
let resolvePromise;
executor(function cancel(message, config, request) {
if (token.reason) { // 存在reason说明已经调用过
return
}
token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason)
})
token.promise = new Promise((resolve, reject) => {
resolvePromise = resolve
})
this.promise.then(cancel => {
if (!token._listeners) return;
let i = token._listeners.length;
while (i-- > 0) {
token._listeners[i](cancel); // 执行所有订阅的函数,其中包括在adapter中存储的cancel
}
token._listeners = null;
});
}
subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}
}
  • 总而言之,取消函数是通过向外传递一个变量cancel,该变量又被赋值为一个函数,该函数被调用就会给当前this(也就是token)存放一个reason标志已经调用过,执行promise的resolve方法,从而执行所有订阅的函数,包含了adapter中存储的cancel(也就是会执行request.abort导致request所在的promise状态变为reject)

原文链接:https://juejin.cn/post/7349068175900016674 作者:xxyCoder

(0)
上一篇 2024年3月23日 下午5:09
下一篇 2024年3月24日 上午10:00

相关推荐

发表回复

登录后才能评论