Реализация промежуточного программного обеспечения серии KOA2 compose (луковая модель)

внешний интерфейс JavaScript React.js Promise

предисловие

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.

Файл: app.js
// 模拟 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.js
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Пример теста выглядит следующим образом (синхронно вверх, асинхронно вниз).

Файл: sync-test.js
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
Файл: async-test.js
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.js
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.js
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.js
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.js
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.js
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.js
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Такому элегантному и качественному коду предстоит пройти долгий путь, я желаю вам «навсегда уйти» по дороге технологий.

Оригинальный текст взят с: https://www.pandashen.com