Redux, koa, сравнительный анализ реализации промежуточного программного обеспечения Express

Express React.js koa Redux

если у тебя естьexpress ,koa, reduxопыта, вы обнаружите, что у них есть中间件(middlewares)Концепция чего-либо,中间件Это идея перехватчика, который используется для добавления некоторой дополнительной обработки между конкретным входом и выходом, не влияя на исходную операцию.

первый контакт中间件используется на стороне сервераexpressа такжеkoa, затем перешел от сервера к внешнему интерфейсу и увидел, чтоreduxДизайн также получил большую игру.中间件Идея дизайна также обеспечивает гибкую и мощную расширяемость для многих фреймворков.

Главное сравнение этой статьиredux, koa, expressРеализация промежуточного программного обеспечения, чтобы быть более интуитивно понятным, я извлеку три中间件Соответствующий базовый код упрощается, и пишется пример моделирования. пример будет держатьexpress, koa,reduxОбщая структура , постарайтесь сохранить такую ​​же, как исходный код, поэтому в этой статье она также будет немного объяснена.express, koa, reduxОбщая структура и ключевая реализация:

Пример адреса исходного кода, Вы можете читать статью, глядя на исходный код, добро пожаловать!

Эта статья подойдет разработчикам, имеющим определенное понимание и опыт работы с express, koa и redux.

промежуточное ПО на стороне сервера

expressа такжеkoaПромежуточное ПО предназначено для обработкиhttpзапрос и ответ, но дизайнерские идеи у них действительно разные. большинство людей знаютexpressа такжеkoaРазличия промежуточного программного обеспечения:

  • expressИспользуя метод «хвостовой рекурсии», промежуточное программное обеспечение выполняется один за другим последовательно, и оно привыкло кresponseОтвет записывается в последнем промежуточном программном обеспечении;
  • а такжеkoaподдержка промежуточного программного обеспеченияgenerator, порядок выполнения - модель "луковичное кольцо".

Модель так называемого «лукового кольца»:

Но на самом деле,expressПромежуточное ПО также может формировать модель «луковичное кольцо», т.nextКод, написанный после вызова, также будет выполнен, ноexpressОбычно этого не делают, потому чтоexpressизresponseОбычно в последнем промежуточном программном обеспечении, затем в другом промежуточном программном обеспеченииnext()Последний код не влияет на окончательный результат ответа;

express

Первый взглядexpressРеализация:

Вход

// express.js

var proto = require('./application');
var mixin = require('merge-descriptors');

exports = module.exports = createApplication;

function createApplication() {

 
  // app 同时是一个方法,作为http.createServer的处理函数
  var app = function(req, res, next) { 
      app.handle(req, res, next)
  }
  
  mixin(app, proto, false);
  return app
}

На самом деле здесь все очень просто, простоcreateApplicationметод созданияexpressНапример, обратите внимание на возвращаемое значениеappЭто объект-экземпляр со множеством смонтированных на нем методов, а также сам метод, т.к.http.createServerФункция обработки, конкретный код находится вapplication.jsсередина:

// application.js

var http = require('http');
var flatten = require('array-flatten');
var app = exports = module.exports = {}

app.listen = function listen() {
  var server = http.createServer(this)
  return server.listen.apply(server, arguments)
}

здесьapp.listenпередачаnodejsизhttp.createServerСоздайтеwebУслуги можно посмотреть здесьvar server = http.createServer(this)вthisкоторыйappсам, то настоящий обработчик, т.е.app.handle;

Обработка промежуточного программного обеспечения

expressПо сути, менеджер промежуточного программного обеспечения, при входеapp.handleЭто время, когда выполняется промежуточное программное обеспечение, поэтому две наиболее важные функции:

  • app.handleПромежуточное ПО хвостового рекурсивного вызова для обработки req и res
  • app.useДобавить промежуточное ПО

поддерживать глобальныйstackМассив используется для хранения всего промежуточного программного обеспечения,app.useРеализация очень проста, это может быть всего одна строка кода ``


// app.use
app.use = function(fn) {
	this.stack.push(fn)
}

expressКонечно, реальная реализация не так проста, в ней есть встроенные функции маршрутизации, в т.ч.router, route, layerТри ключевых класса, сrouterбыть правымpathманевровый,stackхранится вlayerпример,app.useФактически вызываемый методrouterпримерuseметода, те, кто заинтересован, могут прочитать его самостоятельно.

app.handleВот такstackмассив для обработки


app.handle = function(req, res, callback) {

	var stack = this.stack;
	var idx = 0;
	function next(err) {
		if (idx >= stack.length) {
		  callback('err') 
		  return;
		}
		var mid;
		while(idx < stack.length) {
		  mid = stack[idx++];
		  mid(req, res, next);
		}
	}
	next()
}

Это то, что называется "хвостовой рекурсивный вызов",nextспособ вывезтиstackФункция "промежуточного программного обеспечения" в вызове, при этом помещаяnextпередается в «промежуточное программное обеспечение» в качестве третьего параметра, фиксированная форма каждого контракта промежуточного программного обеспечения(req, res, next) => {}, так что каждой функции "промежуточного ПО" нужно вызывать толькоnextметод может быть передан для вызова следующего промежуточного программного обеспечения.

Причина, по которой это называется «хвостовой рекурсией», заключается в том, что последний оператор рекурсивной функции должен вызвать саму функцию, поэтому последний оператор каждого промежуточного программного обеспечения должен бытьnext()Для формирования "хвостовой рекурсии", иначе это обычная рекурсия.Преимущество "хвостовой рекурсии" перед обычной "рекурсией" в том, что она экономит место в памяти и не формирует глубоко вложенные стеки вызовов функций. Если вам интересно, вы можете прочитать книгу г-на Жуана.оптимизация хвостового вызова

Слишком далеко,expressРеализация промежуточного программного обеспечения завершена.

koa

Я должен сказать, по сравнению сexpressС точки зрения,koaОбщий дизайн и реализация кода более совершенны и усовершенствованы; код основан наES6внедрять, поддерживатьgenerator(async await), без встроенной реализации маршрутизации и какого-либо встроенного ПО промежуточного слоя,contextДизайн тоже очень умный.

в общем и целом

Всего 4 файла:

  • application.jsВходной файл, класс экземпляра приложения koa
  • context.js ctxНапример, много проксиrequestа такжеresponseсвойства и методы , переданные как глобальный объект
  • request.js koaк родномуreqинкапсуляция объекта
  • response.js koaк родномуresинкапсуляция объекта

request.jsа такжеresponse.jsНечего сказать, любой веб-фреймворк предоставитreqа такжеresпакет для упрощения обработки. Так что смотрите в основномcontext.jsа такжеapplication.jsреализация

// context.js 

/**
 * Response delegation.
 */

delegate(proto, 'res')
  .method('setHeader')

/**
 * Request delegation.
 */

delegate(proto, 'req')
  .access('url')
  .setter('href')
  .getter('ip');

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

Кратко объясните здесь значение прокси, напримерdelegate(proto, 'res').method('setHeader')Что делает это утверждение:Когда вызывается proto.setHeader, вызывается proto.res.setHeaderТо есть будетprotoизsetHeaderделегировать методprotoизresСвойства, другие аналогичны.

// application.js 中部分代码

constructor() {
	super()
	this.middleware = []
	this.context = Object.create(context)
}

use(fn) {
	this.middleware.push(fn)
}

listen(...args) {
	debug('listen')
	const server = http.createServer(this.callback());
	return server.listen(...args);
}

callback() {
	// 这里即中间件处理代码
	const fn = compose(this.middleware);
	
	const handleRequest = (req, res) => {
	  // ctx 是koa的精髓之一, req, res上的很多方法代理到了ctx上, 基于 ctx 很多问题处理更加方便
	  const ctx = this.createContext(req, res);
	  return this.handleRequest(ctx, fn);
	};
	
	return handleRequest;
}

handleRequest(ctx, fnMiddleware) {
	ctx.statusCode = 404;
	const onerror = err => ctx.onerror(err);
	const handleResponse = () => respond(ctx);
	return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
   

то же самое вlistenметод созданwebобслуживание, не используетсяexpressтак наоборот,const server = http.createServer(this.callback());использоватьthis.callback()генерироватьwebобработчик службы

callbackвозврат функцииhandleRequest, поэтому настоящий обработчикthis.handleRequest(ctx, fn)

Обработка промежуточного программного обеспечения

КонструкторconstructorПоддерживать глобальный массив промежуточного ПО вthis.middlewareи глобальныйthis.contextЭкземпляр (также в исходном коде есть объекты запроса, ответа и некоторые другие вспомогательные свойства). а такжеexpressотличается, потому что нетrouterреализация, всеthis.middlewareявляются обычными функциями «промежуточного программного обеспечения», а не сложнымиlayerпример,

this.handleRequest(ctx, fn);серединаctxпервый параметр,fn = compose(this.middleware)В качестве второго параметраhandleRequestпозвонюfnMiddleware(ctx).then(handleResponse).catch(onerror);Таким образом, ключом к промежуточной обработке являетсяcomposeметод, который является автономным пакетомkoa-compose, выньте его и посмотрите, что внутри:

// compose.js

'use strict'

module.exports = compose

function compose (middleware) {

  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      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)
      }
    }
  }
}


а такжеexpressсерединаnextОн не похож, а он естьpromiseФормально из-за того, что он должен поддерживать асинхронность, понять его немного труднее: каждый中间件Являетсяasync (ctx, next) => {}, который возвращаетpromise, второй параметрnextценностьdispatch.bind(null, i + 1), используется для переноса исполнения «мидлваров», один за другим мидлвары выполняются внутрь, пока не будет выполнен последний промежуточный код,resolveудаляется, его предыдущее «промежуточное ПО» затем выполняетсяawait next()код после, затемresolveБросьте, продолжайте двигаться вперед до первого «мидлвара»resolveвыключено, в конечном итоге делая крайнийpromise resolveТерять.

здесь иexpressочень отличается, чтоkoaОбработка ответа не в "промежуточном программном обеспечении", а возвращается после выполнения промежуточного программного обеспечения.promise resolveназад:

return fnMiddleware(ctx).then(handleResponse).catch(onerror);

пройти черезhandleResponseНаконец, ответ обрабатывается, и «промежуточное ПО» будет установлено.ctx.body, handleResponseтакже будет в основном заниматьсяctx.body,такkoaБудет установлена ​​модель «луковое кольцо»,await next()Следующий код также влияет на окончательный ответ.

На этом промежуточная реализация koa завершена.

redux

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

Это лучшая документация, которую я когда-либо читал, без исключения, она ясно объясняетredux middlewareПроцесс эволюции прекрасно выводит分析问题прибыть解决问题, и постоянно оптимизировать мыслительный процесс.

в общем и целом

В этой статье по-прежнему в основном рассматривается его реализация промежуточного программного обеспечения, сначала кратко поговорим о нем.reduxОсновная логика обработкиcreateStoreэто его программа входа, фабричный метод, возвращающийstoreпример,storeСамый критический метод экземпляраdispatch, а такжеdispatchВсе, что нужно, это одно:

currentState = currentReducer(currentState, action)

то есть позвонитьreducer, пройти в текущемstateа такжеactionвернуть новыйstate.

Таким образом, чтобы смоделировать основныеreduxреализовать толькоcreateStore , dispatchметод. другой контент, напримерbindActionCreators, combineReducersтак же какsubscribeМониторинг является вспомогательной функцией и может временно игнорироваться.

Обработка промежуточного программного обеспечения

Затем идет основная часть реализации «промежуточного программного обеспечения», а именноapplyMiddleware.js:

// applyMiddleware.js

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

reduxРасширения, предоставляемые промежуточным программным обеспечением, находятся вactionПосле начала, прибывreducerРаньше идея его реализации была такой же, какexpress,koaЧто-то другое, не проходит инкапсуляцияstore.dispatch, добавьте перед ним中间件处理程序, но путем рекурсивной перезаписиdispatch, постоянно передавая последний переопределенныйdispatchреализовать.

КаждыйreduxПромежуточное ПО представлено в видеstore => next => action => { xxx }

Здесь в основном два уровня вложенности функций:

  • Самая внешняя функция получает параметрыstore, соответствующийapplyMiddleware.jsКод обработки вconst chain = middlewares.map(middleware => middleware(middlewareAPI)), middlewareAPIвходящийstore. Этот слой долженstoreизapiПередано промежуточному программному обеспечению для использования, в основном дваapi:

    1. getState, пройти напрямуюstore.getState.
    2. dispatch: (...args) => dispatch(...args),Реализация здесь очень умная, неstore.dispatch, а внешняя переменнаяdispatch, эта переменная, наконец, указывает на перезаписанныйdispatch, причина этого в том, что дляredux-thunkТакое асинхронное промежуточное ПО, внутренний вызовstore.dispatchКогда еще после прохождения всего "мидлвара".
  • вернутьchainЭто массив второго слоя, и каждый элемент массива является такой функциейnext => action => { xxx }, эту функцию можно понимать как接受一个dispatchвернутьdispatch, принятыйdispatchвозвращается последним промежуточным ПОdispatch.

  • Еще одной ключевой функцией являетсяcompose, основная функцияcompose(f, g, h)вернуть() => f(g(h(..args)))

теперь понятьdispatch = compose(...chain)(store.dispatch)относительно простой, роднойstore.dispatchПередайте последнее «мидлваре», верните новыйdispatch, а затем передается предыдущему промежуточному ПО до окончательногоdispatch, когда перезаписываетсяdispatchПри вызове выполнение каждого «промежуточного программного обеспечения» представляет собой модель «лукового кольца» снаружи внутрь.

На этом промежуточное ПО redux завершено.

Другие ключевые моменты

reduxЕсть еще один момент в реализации промежуточного программного обеспечения, который стоит изучить: чтобы «промежуточное программное обеспечение» применялось только один раз,applyMiddlewareне действует наstoreпример, но действуйтеcreateStoreзаводской метод. Как понять это? еслиapplyMiddlewareтакое, что

(store, middlewares) => {}

затем, когда несколько вызововapplyMiddleware(store, middlewares)Одно и то же промежуточное ПО будет неоднократно добавляться к одному и тому же экземпляру. такapplyMiddlewareв виде

(...middlewares) => (createStore) => createStore,

Таким образом, каждый раз при применении ПО промежуточного слоя создается новый экземпляр, что позволяет избежать проблемы повторного применения ПО промежуточного слоя.

Эта форма получитmiddlewaresвернутьcreateStoreМетод высшего порядка , этот метод обычно называютcreateStoreизenhanceметод, приложение промежуточного программного обеспечения добавляется внутри, вы найдете этот метод и второй уровень промежуточного программного обеспечения(dispatch) => dispatchсогласуется с формой, поэтому его также можно использовать дляcomposeСделайте несколько улучшений. в то же времяcreateStoreЕсть еще третий параметрenhanceДля внутреннего суждения, самоутверждения. такreduxИспользование промежуточного программного обеспечения может быть записано двумя способами:

第一种:用 applyMiddleware 返回 enhance 增强 createStore

store = applyMiddleware(middleware1, middleware2)(createStore)(reducer, initState)

第二种: createStore 接收一个 enhancer 参数用于自增强

store = createStore(reducer, initState, applyMiddleware(middleware1, middleware2))

Второе использование будет более интуитивным и более читабельным.

На протяженииreduxРеализация функционального программирования ярко отражена в форме промежуточного программного обеспечения.store => next => action => { xx }Это гибкое воплощение каррирования функций, которое преобразует несколько параметров в одиночные параметры, которые можно использовать для предварительной фиксации.storeпараметры, чтобы получить более явную формуdispatch => dispatch, так чтоcomposeбыть в состоянии функционировать.

Суммировать

В общем и целом,expressа такжеkoaРеализация очень похожа, обеnextПередача методов делает рекурсивные вызовы, ноkoaдаpromiseформа.reduxПо сравнению с двумя предыдущими, он немного отличается.Во-первых, он перезаписывается рекурсией для формирования рекурсивного внутреннего вызова во время выполнения.

Суммируйте ключевые сходства и различия трех (не ограничиваясь промежуточным ПО):

  1. Создание экземпляра:expressИспользуя фабричный метод,koaэто класс
  2. koaРеализованный синтаксис более продвинутый, с использованиемES6,служба поддержкиgenerator(async await)
  3. koaнет встроенногоrouter, повысилсяctxГлобальный объект, общий код более лаконичен и удобен в использовании.
  4. koaРекурсия промежуточного программного обеспеченияpromiseформа,expressиспользоватьwhileпетля плюсnextхвостовая рекурсия
  5. я предпочитаюreduxРеализация промежуточного программного обеспечения каррирования более краткая и гибкая, а функциональное программирование более очевидное.
  6. reduxкdispatchУлучшение промежуточного ПО путем переопределения

Наконец-то снова прикрепилИсходный код примера моделированияДля справки по обучению, если вам это нравится, добро пожаловать в звездочку и форк!

ответь на вопрос

кто-то сказал,expressтакже можно использовать вasync functionКак промежуточное ПО для асинхронной обработки?На самом деле невозможно, т.к.expressВыполнение промежуточного программного обеспечения является синхроннымwhileцикл, когда промежуточное ПО также содержит普通函数а такжеasync 函数, порядок выполнения будет нарушен, сначала посмотрите на этот пример:


function a() {
  console.log('a')
}

async function b() {
  console.log('b')
  await 1
  console.log('c')
  await 2
  console.log('d')
}

function f() {
	a()
	b()
	console.log('f')
}

Выход здесь'a' > 'b' > 'f' > 'c'

Вызовите его непосредственно в обычной функцииasyncфункция,asyncФункция будет выполняться синхронно до первогоawaitкод после, а затем немедленно возвращаетpromise, подождите, пока все внутренниеawaitАсинхронное завершение, весьasyncПосле выполнения функцииpromiseбудуresolveТерять.

Таким образом, с помощью приведенного выше анализаexpressРеализация промежуточного программного обеспечения, если используетсяasyncФункция промежуточного программного обеспечения, используемая внутриawaitВыполните асинхронную обработку, тогда следующее промежуточное ПО будет выполнено первым и подождет, покаawaitперезвонить послеnextИндекс будет превышен! , вы можете все сами здесьexpress asyncОткройте аннотации и попробуйте сами.

10.03.2020 Исправление ошибки

Упрощенный макет экспресса неверен

while(idx < stack.length) {
  mid = stack[idx++];
  mid(req, res, next);
}

был исправлен на


  mid = stack[idx++];
  mid(req, res, next);

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