Как реализовать библиотеку HTTP-запросов — чтение и анализ исходного кода axios

внешний интерфейс HTTP Promise axios

Обзор

В процессе фронтенд-разработки мы часто сталкиваемся с ситуациями, когда нужно отправлять асинхронные запросы. Использование библиотеки HTTP-запросов с полными функциями и полными интерфейсами может значительно снизить наши затраты на разработку и повысить эффективность нашей разработки.

axios — очень популярная в последние годы библиотека HTTP-запросов, в настоящее времяGitHubУ него уже более 40 тысяч звезд, и его рекомендуют все.

Сегодня давайте посмотрим, как устроены axios, и что нам предстоит узнать. Когда я писал эту статью, версия axios была 0.18.0. Мы используем эту версию кода в качестве примера для чтения и анализа конкретного исходного кода. В настоящее время все исходные файлы axios находятся вlibпапку, поэтому пути, которые мы упомянули ниже, относятся кlibпуть в папке.

Основное содержание этой статьи:

  • как использовать аксиомы
  • Как спроектирован и реализован основной модуль axios (запрос, перехватчик, отзыв)
  • На чем стоит поучиться дизайну axios?

как использовать аксиомы

Чтобы понять структуру аксиом, нам сначала нужно посмотреть, как аксиомы используются. Мы представляем следующий API axios на простом примере.

послать запрос

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

Вот официальный пример API. Из приведенного выше кода мы видим, что использование axios очень похоже на использование ajax в jQuery, как путем возврата промиса (вы также можете передать обратный вызов успеха, но рекомендуется использовать промис или ожидание) для продолжения последующих операций. .

Этот пример кода очень простой, я не буду вдаваться в подробности, давайте посмотрим, как добавить функцию фильтра.

Функция добавления перехватчиков (Interceptors)

// 增加一个请求拦截器,注意是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);
  });

Из приведенного выше примера мы можем узнать, что перед отправкой запроса мы можем выполнить обработку данных в параметре конфигурации запроса, а после ответа на запрос мы также можем выполнить определенные операции с возвращенными данными. В то же время мы можем выполнять специальную обработку ошибок как при сбое запроса, так и при сбое ответа.

Отменить 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('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

Из приведенного выше примера видно, что axios использует предложение о снятии средств на основе CancelToken. Однако в настоящее время это предложение отозвано. Подробнее см.здесь. Конкретный метод реализации ретракции будет объяснен в анализе исходного кода в следующих главах.

Как спроектирован и реализован основной модуль axios

Благодаря приведенным выше примерам я считаю, что у всех есть общее представление о том, как использовать axios. Далее мы проанализируем дизайн и реализацию аксиом по модулю. На следующем рисунке показаны связанные файлы axios, которые мы будем использовать в этом блоге.Если читатели заинтересованы, они могут прочитать соответствующий код клона в сочетании с блогом, что может углубить понимание связанных модулей.

Модуль HTTP-запроса

В качестве основного модуля код, связанный с отправкой запросов axios, находится вcore/dispatchReqeust.jsв файле. Из-за ограниченного места я выберу некоторые ключевые исходные коды для краткого введения ниже:

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

    // 其他源码

    // default adapter是一个可以判断当前环境来选择使用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Чтобы получить модуль, который отправляет запрос, мы также можем заменить собственный модуль, передав функцию адаптера, соответствующую спецификации (хотя это обычно не делается, это также слабосвязанная точка расширения).

существуетdefault.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;
}

Модуль XHR в axios относительно прост. Это инкапсуляция объекта XMLHTTPRequest. Мы не будем вводить здесь слишком много. Заинтересованные студенты могут прочитать его самостоятельно. Код находится вadapters/xhr.jsв файле.

модуль-перехватчик

понялdispatchRequestРеализован модуль отправки HTTP-запросов, посмотрим, как 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 для отправки запросов.Поскольку реализация функции относительно длинная, я кратко расскажу о соответствующих дизайнерских идеях:

  1. chain — это очередь выполнения. Начальное значение этой очереди — обещание с параметром конфигурации.
  2. В очередь выполнения цепочки вставляется начальная функция для отправки запросаdispatchReqeustи соответствующийundefined. Нужно добавить позжеundefinedЭто потому, что в Promise вам нужна функция обратного вызова успеха и ошибки, это из кодаpromise = promise.then(chain.shift(), chain.shift());можно увидеть. следовательно,dispatchReqeustа такжеundefinedМы можем быть парой функций.
  3. В очереди выполнения цепочки функция, отправляющая запросdispatchReqeustнаходится в среднем положении. Ему предшествует перехватчик запросов черезunshiftпомещается метод; за ним следует перехватчик ответа черезpushположить в а. Следует отметить, что эти функции ставятся парами, то есть по две одновременно.

через вышеуказанное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 реализована отмена HTTP-запроса путем передачи метода Promise, но давайте посмотрим на конкретный код:

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) {
            // Cancellation has already been requested
            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
    };
};

пока вadapter/xhr.jsВ файле есть соответствующий код запроса на отмену:

if (config.cancelToken) {
    // 等待取消
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }

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

В сочетании с приведенным выше примером отмены HTTP-запроса и этими кодами давайте кратко поговорим о соответствующей логике реализации:

  1. В запросе, который может потребоваться отменить, мы вызываем исходный метод при инициализации, который возвращаетCancelTokenЭкземпляр класса A и функция отмены.
  2. В исходном методе, возвращающем экземпляр A, инициализируется промис в состоянии ожидания. После того, как мы передаем весь экземпляр A в axios, это обещание используется в качестве триггера для отмены запроса.
  3. Когда вызывается метод отмены, возвращаемый методом-источником, состояние обещания в экземпляре А изменяется с «ожидание» на «выполнено», и функция обратного вызова срабатывает немедленно, тем самым запуская логику отмены axios——request.abort().

На чем стоит поучиться дизайну axios?

Логика обработки функции отправки запроса

Как упоминалось в предыдущей главе, axios обрабатывает запросdispatchRequestПри вызове функции она не рассматривается как специальная функция, а обрабатывается одинаково и помещается в середину очереди, тем самым обеспечивая согласованность обработки очереди и улучшая читаемость кода.

Логика обработки адаптера

В логике обработки адаптера axios не рассматривает модули http и xhr (один используется для отправки запросов Node.js, а другой — для отправки запросов браузером) как собственные модули непосредственно вdispatchRequestПейте прямо вdefault.jsФайл импортируется по умолчанию. Это не только обеспечивает низкую связь между двумя модулями, но и оставляет пользователям возможность настраивать модуль отправки запросов в будущем.

Отменить логику обработки HTTP-запроса

В логике отмены HTTP-запроса axios ловко использует промис в качестве триггера и передает функцию разрешения наружу в виде параметров в обратном вызове. Это может не только обеспечить согласованность внутренней логики, но и гарантировать, что при запросе на отмену нет необходимости напрямую изменять выборочные данные связанных классов, что в наибольшей степени позволяет избежать вторжения в другие модули.

Суммировать

В этой статье подробно рассказывается об использовании, дизайнерских идеях и методах реализации, связанных с аксиомами. Читатели могут понять идеи дизайна axios из приведенных выше статей, и в то же время они могут изучить соответствующий опыт инкапсуляции и взаимодействия модулей в коде axios.

Из-за недостатка места в этой статье разобраны и представлены только основные модули axios.Если вас интересуют другие коды, вы можете перейти кGitHubсмотреть.

Если у вас есть какие-либо вопросы или мнения, пожалуйста, не стесняйтесь оставлять сообщение для обсуждения.