предисловие
Koa
Это текущая основная среда NodeJS, известная своим легким весом, а ее промежуточный механизм похож на относительно традиционный.Express
Поддерживается асинхронность, поэтому ее часто используют при написании кода.async/await
, улучшает читаемость и делает код более элегантным, предыдущий постNodeJS advanced — анализ исходного кода Koa, также по "луковой модели" и её реализацииcompose
анализ, в связи с личнымcompose
Идея программирования более важна и широко используется, поэтому в этой статье используется тема «луковой модели» и предполагается реализовать ее четырьмя способами.compose
.
Чехол для модели Onion
если вы использовалиKoa
Вы должны быть знакомы с термином "луковая модель", этоKoa
Последовательный механизм для промежуточного программного обеспечения и поддерживает асинхронность.Ниже приведен классический случай выражения «луковой модели».
const Koa = require("koa");
const app = new Koa();
app.use(asycn (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(asycn (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.use(asycn (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
app.listen(3000);
// 1
// 3
// 5
// 6
// 4
// 2
Вышеупомянутый метод записи используется в соответствии с официальной рекомендацией.async/await
, но не беда, если синхронный код не используется. Вот простой анализ механизма выполнения. Если первая промежуточная функция выполняетсяnext
, будет выполнено следующее промежуточное ПО и т.д., имеем результат выше, а вKoa
В исходном коде эта функция зависит отcompose
метод, у нас есть четыре реализации в этой статьеcompose
В пути реализованы синхронный и асинхронный, а соответствующие кейсы приложены для проверки.
Готов к работе
в настоящем творенииcompose
Перед методом следует провести некоторые подготовительные работы, такие как созданиеapp
объект для заменыKoa
Создайте экземпляр объекта и добавьтеuse
Массив методов и ПО промежуточного слоя управленияmiddlewares
.
// 模拟 Koa 创建的实例
const app = {
middlewares: []
};
// 创建 use 方法
app.use = function(fn) {
app.middlewares.push(fn);
};
// app.compose.....
module.exports = app;
Вышеупомянутый модуль экспортируетapp
объект и создал промежуточную функцию хранилищаmiddlewares
и добавление промежуточного программного обеспеченияuse
метод, потому что независимо от того, каким образом он реализованcompose
Все это нужно, простоcompose
Логика другая, поэтому следующие блоки кода будут писать толькоcompose
метод.
Реализация компоновки в Коа
Впервые представленоKoa
Реализация в исходном коде, вKoa
Исходный код на самом деле черезkoa-compose
Он реализован промежуточным программным обеспечением. Здесь мы извлекаем основную логику этого модуля и реализуем ее по-своему, потому что основное внимание уделяется анализу.compose
Принцип, такctx
параметр удален, потому что мы не будем его использовать, дело в том,next
параметр.
1. Реализация синхронизации
app.compose = function() {
// 递归函数
function dispatch(index) {
// 如果所有中间件都执行完跳出
if (index === app.middlewares.length) return;
// 取出第 index 个中间件并执行
const route = app.middlewares[index];
return route(() => dispatch(index + 1));
}
// 取出第一个中间件函数执行
dispatch(0);
};
Выше приведена реализация синхронизации через рекурсивную функциюdispatch
Выполнение берет первую промежуточную функцию в массиве и выполняет ее, передает функцию во время выполнения и выполняет ее рекурсивно.dispatch
, входящий параметр+1
, чтобы выполнялась следующая мидлварная функция, и так далее, пока не выполнится весь мидлвар, а она будет выскакивать при невыполнении условий выполнения мидлвара, так что по приведенному выше случаю1 3 5 6 4 2
Пример теста выглядит следующим образом (синхронно вверх, асинхронно вниз).
const app = require("./app");
app.use(next => {
console.log(1);
next();
console.log(2);
});
app.use(next => {
console.log(3);
next();
console.log(4);
});
app.use(next => {
console.log(5);
next();
console.log(6);
});
app.compose();
// 1
// 3
// 5
// 6
// 4
// 2
const app = require("./app");
// 异步函数
function fn() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log("hello");
}, 3000);
});
}
app.use(async next => {
console.log(1);
await next();
console.log(2);
});
app.use(async next => {
console.log(3);
await fn(); // 调用异步函数
await next();
console.log(4);
});
app.use(async next => {
console.log(5);
await next();
console.log(6);
});
app.compose();
Получаем, что если в случае поKoa
Рекомендуемый способ написания, т.е. использованиеasync
функция, пройдет, но дастuse
При передаче параметров вы можете передавать обычные функции илиasync
функции, нам нужно обернуть возвращаемое значение всего промежуточного ПО в Promise, чтобы быть совместимым с обоими случаями, на самом деле, вKoa
серединаcompose
Окончательный возврат тоже Promise, он для последующего написания логики, но он сейчас не поддерживается.Давайте решим эти две задачи.
compose
В других реализациях используйтеsync-test.js
а такжеasync-test.js
Проверка, чтобы потом не повторяться.2. Обновите для поддержки асинхронности
app.compose = function() {
// 递归函数
function dispatch(index) {
// 如果所有中间件都执行完跳出,并返回一个 Promise
if (index === app.middlewares.length) return Promise.resolve();
// 取出第 index 个中间件并执行
const route = app.middlewares[index];
// 执行后返回成功态的 Promise
return Promise.resolve(route(() => dispatch(index + 1)));
}
// 取出第一个中间件函数执行
dispatch(0);
};
мы знаемasync
в функцииawait
Асинхронный код, выполняемый позже, должен реализовать ожидание и продолжать выполняться вниз после асинхронного выполнения.Ему нужно дождаться промиса, поэтому мы вернем успешный промис при вызове каждой промежуточной функции, используяasync-test.js
Проверил и обнаружил, что результат1 3 hello(3s后) 5 6 4 2
.
Реализация компоновки в старых версиях Redux
1. Реализация синхронизации
app.compose = function() {
return app.middlewares.reduceRight((a, b) => () => b(a), () => {})();
};
Приведенный выше код кажется непростым для понимания, мы могли бы также разобрать этот код в зависимости от случая.middlewares
Три функции промежуточного программного обеспечения, хранящиеся вfn1
,fn2
а такжеfn3
, так как с помощьюreduceRight
метод, так что это слияние в обратном порядке, в первый разa
представляет начальное значение (пустая функция),b
представлятьfn3
, при выполненииfn3
Возвращает функцию, которая используется в качестве следующего слиянияa
,а такжеfn2
так какb
и так далее, процесс выглядит следующим образом.
// 第 1 次 reduceRight 的返回值,下一次将作为 a
() => fn3(() => {});
// 第 2 次 reduceRight 的返回值,下一次将作为 a
() => fn2(() => fn3(() => {}));
// 第 3 次 reduceRight 的返回值,下一次将作为 a
() => fn1(() => fn2(() => fn3(() => {})));
Как видно из приведенного выше процесса дизассемблирования, если мы вызовем эту функцию, то она будет выполнена первойfn1
, если звонитьnext
будет выполнятьfn2
, если тот же вызовnext
будет выполнятьfn3
,fn3
Это последняя промежуточная функция, вызовите ее снова.next
выполнит пустую функцию, которую мы изначально передали, поэтомуreduceRight
В качестве начального значения задана пустая функция, которая предотвращает вызов последнего промежуточного программного обеспечения.next
И сообщить об ошибке.
После тестирования приведенный выше код не будет отображаться не по порядку, а вcompose
После выполнения мы хотим выполнить некоторые последующие операции, поэтому мы хотим вернуть промис, и мы хотим перейти кuse
Функция промежуточного программного обеспечения может быть либо обычной функцией, либоasync
функция, это то, что мыcompose
Полностью поддерживается асинхронность.
2. Обновите для поддержки асинхронности
app.compose = function() {
return Promise.resolve(
app.middlewares.reduceRight(
(a, b) => () => Promise.resolve(b(a)),
() => Promise.resolve();
)()
);
};
Что касается процесса синхронного анализа, поскольку в пустой функции, выполняемой после выполнения последнего промежуточного программного обеспечения, не должно быть никакой логики, она может продолжать выполняться при обнаружении асинхронного кода (например, при выполненииnext
потом снова позвонилthen
), перерабатываются в Promise, гарантируяreduceRight
Обещание возвращается в возвращаемой функции каждый раз, когда оно объединяется, поэтому оно полностью совместимо.async
и обычные функции, когда выполняется все промежуточное программное обеспечение, оно также возвращает промис, поэтомуcompose
вы можете позвонитьthen
Метод выполняет последующую логику.
Реализация новой версии Redux compose
1. Реализация синхронизации
app.compose = function() {
return app.middlewares.reduce((a, b) => arg => a(() => b(arg)))(() => {});
};
Redux
новая версия генерал-лейтенантcompose
Внесены некоторые изменения в логику оригинала.reduceRight
заменитьreduce
, то есть обратный порядок сливается с положительным порядком, мы не обязательно иRedux
Исходный код точно такой же, и он основан на той же идее реализации требований последовательного промежуточного программного обеспечения.
Лично я думаю, что это сложнее понять после изменения слияния в положительном порядке, поэтому я все еще разделяю приведенный выше код в сочетании с случаем, а промежуточное программное обеспечение все ещеfn1
,fn2
а такжеfn3
,из-заreduce
Начальное значение не передается, поэтому в настоящее времяa
дляfn1
,b
дляfn2
.
// 第 1 次 reduce 的返回值,下一次将作为 a
arg => fn1(() => fn2(arg));
// 第 2 次 reduce 的返回值,下一次将作为 a
arg => (arg => fn1(() => fn2(arg)))(() => fn3(arg));
// 等价于...
arg => fn1(() => fn2(() => fn3(arg)));
// 执行最后返回的函数连接中间件,返回值等价于...
fn1(() => fn2(() => fn3(() => {})));
Так зоветreduce
Когда последняя функция возвращалась, в качестве параметра была передана пустая функция, фактически этот параметр наконец-то был передан вfn3
, то есть третье промежуточное ПО, которое гарантирует, что последнее промежуточное ПО вызываетnext
не сообщит об ошибке.
2. Обновите для поддержки асинхронности
Следующая более сложная задача — изменить приведенный выше код для поддержки асинхронности, которая реализована следующим образом.
app.compose = function() {
return Promise.resolve(
app.middlewares.reduce((a, b) => arg =>
Promise.resolve(a(() => b(arg)))
)(() => Promise.resolve())
);
};
Асинхронная реализация на самом деле представляет собой подпрограмму со слиянием в обратном порядке, то есть пусть возвращаемое значение каждой промежуточной функции будет Promise, а пустьcompose
Также возвращает обещание.
Реализовано с использованием асинхронных функций
Эта версия - то, что я изучал раньшеKoa
Анализ большого парня случайно, когда исходный кодKoa
Увидел в статье впринципе (долго не мог найти ссылку просматривая), а так же беру здесь и делюсь с вами, т.к.async
Функция реализована, поэтому по умолчанию поддерживается асинхронность, т.к.async
Функция возвращает обещание.
app.compose = function() {
// 自执行 async 函数返回 Promise
return (async function () {
// 定义默认的 next,最后一个中间件内执行的 next
let next = async () => Promise.resolve();
// middleware 为每一个中间件函数,oldNext 为每个中间件函数中的 next
// 函数返回一个 async 作为新的 next,async 执行返回 Promise,解决异步问题
function createNext(middleware, oldNext) {
return async () => {
await middleware(oldNext);
}
}
// 反向遍历中间件数组,先把 next 传给最后一个中间件函数
// 将新的中间件函数存入 next 变量
// 调用下一个中间件函数,将新生成的 next 传入
for (let i = app.middlewares.length - 1; i >= 0; i--) {
next = createNext(app.middlewares[i], next);
}
await next();
})();
};
в коде вышеnext
Это функция, которая возвращает только успешное состояние Promise, которое можно понимать как последний вызов промежуточного программного обеспечения в других реализациях.next
, а массивmiddlewares
Это происходит в обратном порядке, первое полученное значение является последним промежуточным программным обеспечением, а вызовcreateNext
Роль состоит в том, чтобы вернуть новый исполняемый файл последнего промежуточного программного обеспечения в массиве.async
функцию и передать начальныйnext
, это возвращаетasync
функционировать как новыйnext
, а затем получите предпоследнее промежуточное ПО, вызовитеcreateNext
, который возвращаетasync
функция, функция по-прежнему является выполнением предпоследнего промежуточного программного обеспечения, входящегоnext
Он был заново сгенерирован в прошлый разnext
и так далее до первого промежуточного ПО.
Таким образом, выполнение первого промежуточного программного обеспечения возвращаетnext
выполнит переданный в последнем сгенерированномnext
функция, второе промежуточное ПО будет выполнено, а второе промежуточное ПО будет выполненоnext
и так далее до выполнения изначально заданногоnext
, через проверку корпуса результат выполнения точно такой же, как у луковой модели.
Что касается асинхронной задачи, то каждое выполнениеnext
обеasync
Функция возвращает Promise после выполнения, а самое внешнее самовыполнениеasync
Функция также возвращает обещание, то естьcompose
Окончательный результат — обещание, поэтому асинхронность полностью поддерживается.
Суммировать
Может быть, вы подумаете, прочитав эти несколько способов, илиKoa
дляcompose
Метод реализации самый простой для понимания, и вы также можете почувствовать то же, что и я.Redux
Две реализации иasync
Метод реализации функции настолько гениален, именно когда JavaScript критикуется другими как "слабый тип" и "не строгий", он настолько гибкий и творческий, что мы не можем судить, является ли это преимуществом или недостатком ( доброжелательный видит разницу, мудрый видит мудрость), но одно можно сказать точно, изучение JavaScript не должно быть связано «соответствием» строго типизированных языков (личная точка зрения, разработчики строго типизированных языков должны не спрей), это впитывать такие гениальные идеи программирования и писатьcompose
Такому элегантному и качественному коду предстоит пройти долгий путь, я желаю вам «навсегда уйти» по дороге технологий.