Как лучше понять промежуточное ПО и луковую модель

внешний интерфейс
Как лучше понять промежуточное ПО и луковую модель

считаю использованнымKoa,ReduxилиExpressВсе мои друзья знакомы с промежуточным программным обеспечением, особенно при изученииKoaв процессе, также вступит в контакт с«Луковая модель». В этой статье Brother A Bao будет изучать с вами промежуточное ПО Koa, но здесь Brother A Bao не планирует показывать общеизвестную информацию с самого начала.«Схема луковой модели», но сначала представим, что такое промежуточное ПО в Koa?

Прочитайте последние популярные статьи брата А Бао (спасибо за вашу поддержку и поддержку 🌹🌹🌹):

1. Промежуточное ПО Коа

существует@types/koa-composeв упаковкеindex.d.tsВ заголовочном файле находим определение типа промежуточного ПО:

// @types/koa-compose/index.d.ts
declare namespace compose {
  type Middleware<T> = (context: T, next: Koa.Next) => any;
  type ComposedMiddleware<T> = (context: T, next?: Koa.Next) => Promise<void>;
}
  
// @types/koa/index.d.ts => Koa.Next
type Next = () => Promise<any>;

НаблюдаяMiddlewareОпределение типа, мы можем знать, что в Коа промежуточное ПО — это обычная функция, которая получает два параметра:contextиnext. вcontextпредставляет объект контекста, аnextПредставляет объект функции, который при вызове возвращает объект Promise.

Поняв, что такое промежуточное ПО Koa, давайте представим ядро ​​промежуточного ПО Koa, а именноcomposeфункция:

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms || 1));
}

const arr = [];
const stack = [];

// type Middleware<T> = (context: T, next: Koa.Next) => any;
stack.push(async (context, next) => {
  arr.push(1);
  await wait(1);
  await next();
  await wait(1);
  arr.push(6);
});

stack.push(async (context, next) => {
  arr.push(2);
  await wait(1);
  await next();
  await wait(1);
  arr.push(5);
});

stack.push(async (context, next) => {
  arr.push(3);
  await wait(1);
  await next();
  await wait(1);
  arr.push(4);
});

await compose(stack)({});

Источник приведенного выше кода:GitHub.com/Смотри, о, это/compo…

Для приведенного выше кода мы хотим выполнитьcompose(stack)({})После оператора массивarrзначение[1, 2, 3, 4, 5, 6]. Здесь нам все равноcomposeКак реализована функция. Разберем, нужен ли массивarrВыведите желаемый результат, поток выполнения трех вышеуказанных промежуточных программ:

1. Начните выполнение первого промежуточного ПО и вставьте в массив arr 1. В это время значение массива arr равно[1], затем подождите 1 миллисекунду. Чтобы гарантировать, что первый элемент массива arr2, нам нужно позвонитьnextПосле функции начните выполнение второго промежуточного ПО.

2. Начните выполнение второго промежуточного ПО и вставьте в массив arr 2. В это время значение массива arr равно[1, 2], продолжайте ждать 1 миллисекунду. Чтобы убедиться, что второй элемент массива arr3, нам также нужно позвонитьnextПосле функции начните выполнение третьего промежуточного ПО.

3. Начните выполнение третьего промежуточного ПО и поместите в массив arr 3. В это время значение массива arr равно[1, 2, 3], продолжайте ждать 1 миллисекунду. Чтобы гарантировать, что третий элемент массива arr4, требуем, чтобы в середине обращения к третьемуnextПосле функции она должна иметь возможность продолжать выполнение.

4. При выполнении третьего промежуточного ПО значение массива arr равно[1, 2, 3, 4]. Следовательно, чтобы убедиться, что четвертый элемент массива arr равен 5, нам нужно вернуть второе промежуточное ПО после выполнения третьего промежуточного ПО.nextПосле функции начинается выполнение оператора.

5. При выполнении второго промежуточного ПО значение массива arr равно[1, 2, 3, 4, 5]. Точно так же, чтобы убедиться, что пятый элемент массива arr равен 6, нам нужно вернуться к первому промежуточному программному обеспечению после выполнения второго промежуточного программного обеспечения.nextПосле функции начинается выполнение оператора.

6. При выполнении первого промежуточного ПО значение массива arr равно[1, 2, 3, 4, 5, 6].

Для более интуитивного понимания описанного выше процесса выполнения мы можем рассматривать каждое промежуточное ПО как большую задачу, а затемnextФункция является точкой разделения, и каждая большая задача разбирается на 3beforeNext,nextиafterNext3 небольших задания.

На приведенном выше рисунке мы начинаем с промежуточного программного обеспечения.beforeNextЗадача начнет выполняться, а затем следуйте шагам выполнения, указанным фиолетовой стрелкой, чтобы завершить планирование задач промежуточного программного обеспечения. существуетКакие уроки можно извлечь из проекта 77.9K Axios?В этой статье брат А Бао изРегистрация задач, планирование задач и планирование задач3 аспекта для анализаAxiosРеализация перехватчика. Точно так же Brother Abao проанализирует механизм промежуточного программного обеспечения Koa с точки зрения трех вышеупомянутых аспектов.

1.1 Регистрация задачи

В Koa, после создания объекта приложения Koa, мы можем вызвать объектuseспособ регистрации промежуточного ПО:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

фактическиuseРеализация метода очень проста, т.lib/application.jsфайл, находим его определение:

// lib/application.js
module.exports = class Application extends Emitter {  
  constructor(options) {
    super();
    // 省略部分代码 
    this.middleware = [];
  }
  
 use(fn) {
   if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
   // 省略部分代码 
   this.middleware.push(fn);
   return this;
  }
}

Как видно из приведенного выше кода, вuseВнутри метода будетfnПараметр проверяется на тип, при прохождении проверкиfnПромежуточное ПО, на которое указывает, сохраняется вmiddlewareмассив, а также возвращаетthisобъект, который поддерживает цепочку вызовов.

1.2 Планирование задач

существуетКакие уроки можно извлечь из проекта 77.9K Axios?В этой статье Brother Abao ссылается на модель конструкции перехватчика Axios и описывает следующие общие модели обработки задач:

В этой общей модели Brother Abao выполняет планирование задач, размещая препроцессор и постпроцессор до и после основных задач CoreWork соответственно. Для механизма промежуточного программного обеспечения Koa это достигается размещением препроцессора и постпроцессора соответственно вawait next()До и после утверждения, чтобы завершить расположение задачи.

// 统计请求处理时长的中间件
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

1.3 Планирование задач

Из предыдущего анализа мы уже знаем, что использованиеapp.useПромежуточное ПО, зарегистрированное методом, будет сохранено во внутреннемmiddlewareв массиве. Чтобы завершить планирование задач, нам нужно постоянно начинать сmiddlewareВыньте промежуточное ПО из массива для выполнения. Алгоритм планирования промежуточного программного обеспечения инкапсулирован вkoa-composeв упаковкеcomposeВ функции конкретная реализация функции выглядит следующим образом:

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */
function compose(middleware) {
  // 省略部分代码
  return function (context, next) {
    // last called middleware #
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
      if (i <= index)
        return Promise.reject(new Error("next() called multiple times"));
      index = i;
      let fn = middleware[i];
      if (i === middleware.length) fn = next;
      if (!fn) return Promise.resolve();
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

composeФункция принимает один параметр, тип параметра — массив, и при вызове функции возвращается новая функция. Далее мы возьмем предыдущий пример в качестве примера для анализаawait compose(stack)({});Исполнение заявления.

1.3.1 dispatch(0)

Как видно из рисунка выше, при вызове первого промежуточного ПОnextфункция, по сути, продолжает вызыватьdispatchфункция, параметры в это времяiзначение1.

1.3.2 dispatch(1)

Как видно из рисунка выше, при вызове второго промежуточного ПО внутриnextфункция, все еще вызывающаяdispatchфункция, параметры в это времяiзначение2.

1.3.3 dispatch(2)

Как видно из рисунка выше, при вызове третьего мидлвара внутриnextфункция, все еще вызывающаяdispatchфункция, параметры в это времяiзначение3.

1.3.4 dispatch(3)

Как видно из приведенного выше рисунка, когдаmiddlewareПосле того, как промежуточное ПО в массиве начнет выполняться, если планирование не задано явноnextзначение параметра, он начнет возвращатьnextОператор после функции продолжает выполняться. Когда третье промежуточное ПО будет выполнено, оно вернется ко второму промежуточному ПО.nextОператоры после функции продолжают выполняться до тех пор, пока не будут выполнены все операторы, определенные в промежуточном программном обеспечении.

После анализаcomposeКод реализации функции, давайте посмотрим, как Koa использует его внутриcomposeФункция для обработки зарегистрированного промежуточного ПО.

const Koa = require('koa');
const app = new Koa();

// 响应
app.use(ctx => {
  ctx.body = '大家好,我是阿宝哥';
});

app.listen(3000);

Используя приведенный выше код, я могу быстро запустить сервер. вuseМы уже разобрали этот метод ранее, так что давайте проанализируем его далее.listenметод, реализация которого заключается в следующем:

// lib/application.js
module.exports = class Application extends Emitter {  
  listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
}

очевидно вlistenВнутри метода он сначала вызовет встроенный HTTP-модуль Node.js.createServerспособ создать сервер, а затем начать слушать указанный порт, то есть начать ждать клиентских подключений. Кроме того, при вызовеhttp.createServerКогда метод создает HTTP-сервер, мы передаем следующие параметры:this.callback(), конкретная реализация этого метода выглядит следующим образом:

// lib/application.js
const compose = require('koa-compose');

module.exports = class Application extends Emitter {  
  callback() {
    const fn = compose(this.middleware);
    if (!this.listenerCount('error')) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }
}

существуетcallbackВнутри метода мы, наконец, видим давно потерянноеcomposeметод. при звонкеcallbackПосле метода он вернетhandleRequestФункциональные объекты используются для обработки HTTP-запросов. Вызывается всякий раз, когда сервер Koa получает запрос клиента.handleRequestметод, который сначала создаст новый объект Context, а затем выполнит зарегистрированное промежуточное ПО для обработки полученного HTTP-запроса:

module.exports = class Application extends Emitter {  
  handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
}

Хорошо, содержание промежуточного программного обеспечения Koa в основном было представлено, те, кто интересуется ядром Koa, могут изучить его самостоятельно. Далее мы представим модель лука и ее приложения.

Следуйте «Дорога полного стека», чтобы прочитать другие статьи по анализу исходного кода и более 50 учебных пособий «Relearn TS».

Луковая модель

2.1 Введение в луковую модель

(Источник изображения:яйцо js.org/ru/intro/spoof…

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

2.2 Применение луковой модели

Помимо применения луковой модели в Koa, модель также широко используется в некоторых хороших проектах на Github, таких какkoa-routerи Alibabamidway,umi-requestи т.д. проекты.

После представления промежуточного программного обеспечения и луковой модели Koa брат Абао выводит следующие общие модели обработки задач в соответствии со своим пониманием:

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

// x-response-time
async function responseTime(ctx, next) {
  const start = new Date();
  await next();
  const ms = new Date() - start;
  ctx.set("X-Response-Time", ms + "ms");
}

Для каждого промежуточного программного обеспечения препроцессор и постпроцессор являются необязательными. Например, для установки унифицированного содержимого ответа используется следующее промежуточное ПО:

// response
async function respond(ctx, next) {
  await next();
  if ("/" != ctx.url) return;
  ctx.body = "Hello World";
}

Хотя два промежуточных ПО, описанных выше, относительно просты, вы также можете реализовать сложную логику в соответствии со своими потребностями. Ядро Коа очень легкое, а воробей маленький и цельный. Предоставляя элегантный механизм промежуточного программного обеспечения, он позволяет разработчикам гибко расширять функции веб-сервера.Эта дизайнерская идея достойна нашего изучения и ссылки. Хорошо, на этот раз я сначала представлю это здесь, если будет возможность, позже брат Абао представит это отдельно.ReduxилиExpressпромежуточный механизм.

3. Справочные ресурсы