1. 程式人生 > 實用技巧 >node 淺析koa2中介軟體

node 淺析koa2中介軟體

koa2採用裡asyncawait來處理非同步,koa2例項的use函式的引數都是中介軟體。

先來看一個koa2的核心小demo

// 中介軟體的倉庫
const arr = [
        async (next) => {
            console.log(1);
            await next();
            console.log(2);
        },
        async (next) => {
            console.log(3);
            await new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(
                        console.log(4)
                    );
                }, 1000);
            }); // 非同步操作 await 會等待後面的promise resolve 後再向下執行
            await next();
            console.log(5);
        },
        async (next) => {
            console.log(6);
        },
        async (next) => {
            // 不會執行 因為上一個函式中沒有執行next
            console.log(7);
            await next();
            console.log(8);
        },
        async (next) => {
            // 不會執行 因為前面的函式中沒有執行next
            console.log(9);
        }
    ];

    function fun(arr) {
        function dispose(index) {
            const currentFun = arr[index];
            const next = dispose.bind(null, index + 1);
            return currentFun(next); // 尾遞迴
        }

        dispose(0);
    }

    fun(arr); // 先列印 1 3 一秒後列印4 6 5 2

code開始 (新建一個ware.js檔案)

const http = require('http');
const urlParser = require("url"); // 解析url字串和url物件

class Middleware {
    constructor() {
        this.wares = [];  // 儲存中介軟體
    };

    use(fun) {
        this.wares.push(fun); // 收集中介軟體
        return this;
    };

    /* 中介軟體處理的核心 */
    handleMiddleware(wareList) {
        return function (ctx) {
            // 中介軟體呼叫
            function dispose(index) {
                const currentFun = wareList[index];
                try {
                    // 使用Promise.resolve 包裝 currentFun 防止外部傳入的currentFun為一個普通函式
                    return Promise.resolve(
                        /* dispose.bind(null, index + 1)就是next 讓dispose繼續執行下一個中介軟體
                        如果沒有在中介軟體中呼叫dispose.bind(null, index + 1) 則不會再去獲取下一個中介軟體
                        */
                        currentFun(ctx, dispose.bind(null, index + 1))
                    );
                } catch (e) {
                    return Promise.reject(e);
                }
            }

            // 立即執行一下倉庫的第一個中介軟體
            dispose(0);
        }
    };

    createContext(req, res) {
        const {method, url} = req;
        const {query} = urlParser.parse(url, true);

        // ... 這裡遠比這個複雜, 我們只做一個簡單的包裝
        return {
            method, url, query,
            res
        };
    }

    serverHandle() {
        return (req, res) => {
            // 當請求來的時候我們去觸發中介軟體
            const fn = this.handleMiddleware(this.wares);
            // 得到當前請求的上下文物件
            const ctx = this.createContext(req, res);
            fn(ctx);
        }
    };

    listen(...args) {
        const app = http.createServer(this.serverHandle()); // 這裡只是為了模擬得到一個http服務
        app.listen(...args); // 直接交給node原生的http模組處理
    };
}

module.exports = Middleware;

測試 (同一目錄下新建一個demo.js檔案)

const Demo = require("./ware");

const app = new Demo();

app.use(async (ctx, next) => {
    await next();
    console.log(`${ctx.method} ${ctx.url}`);
});

app.use(async ctx => {
    ctx.res.end("hello world")
});

app.listen(5000, () => {
    console.log("http://localhost:5000");
});

> cmd執行 node demo.js 瀏覽器訪問 http://localhost:5000 => hello world