摘要:所以方法實現如下每次有新的請求,我們都需要把這次請求的上下文灌進數組中的每一個中間件里。筆者在寫了這個分鐘以后去看了源碼,發現實現思路基本就是這樣,相信經過我的這個分鐘的洗禮,你去看源碼一樣小菜一碟。
原文地址
周五組內同學討論搞一些好玩的東西,有人提到了類似『5分鐘實現koa』,『100行實現react』的創意,仔細想了以后,5分鐘實現koa并非不能實現,遂有了這篇博客。準備
先打開koa官網,隨意找出了一個代表koa核心功能的的demo就可以,如下
const Koa = require("koa"); const app = new Koa(); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set("X-Response-Time", `${ms}ms`); }); // logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async ctx => { ctx.body = "Hello World"; }); app.listen(3000);
最終要實現的效果是實現的一個5min-koa模塊,直接將代碼中第一行替換為const Koa = require("./5min-koa");,程序可以正常執行就可以了。
Koa的核心通過koa官網得知,app.listen方法實際上是如下代碼的簡寫
const http = require("http"); const Koa = require("koa"); const app = new Koa(); http.createServer(app.callback()).listen(3000);
所以我們可以先把app.listen實現出來
class Koa { constructor() {} callback() { return (req, res) => { // TODO } } listen(port) { http.createServer(this.callback()).listen(port); } }
koa的核心分為四部分,分別是
context 上下文
middleware 中間件
request 請求
responce 響應
Context我們先來實現一個最簡化版的context,如下
class Context { constructor(app, req, res) { this.app = app this.req = req this.res = res // 為了盡可能縮短實現時間,我們直接使用原生的res和req,沒有實現ctx上的ctx.request ctx.response // ctx.request ctx.response只是在原生res和req上包裝處理了一層 } // 實現一些demo中使用到的ctx上代理的方法 get set() { return this.res.setHeader } get method() { return this.req.method } get url() { return this.req.url } }
這樣就完成了一個最基本的Context,別看小,已經夠用了。
每一次有新的請求,都會創建一個新的ctx對象。
koa的中間件是一個異步函數,接受兩個參數,分別是ctx和next,其中ctx是當前的請求上下文,next是下一個中間件(也是異步函數),這樣想來,我們需要一個維護中間件的數組,每次調用app.use就是往數組中push一個一步函數。所以use方法實現如下
use(middleware) { this.middlewares.push(middleware) }
每次有新的請求,我們都需要把這次請求的上下文灌進數組中的每一個中間件里。單單灌進ctx還不夠,還要使每個中間件都能通過next函數調用到下一個中間件。當我們調用next函數時,一般是不需要傳參數的,而被調用的中間件中一定會接收到ctx和next兩個參數。
調用方不需要傳參,被調用方卻能接到參數,這讓我立刻想到bind方法,只要將每一個中間件所需要的ctx和next都提前綁定好,問題就解決了。下面的代碼就是通過bind方法,將用戶傳入的middleware列表轉換成next函數列表
let bindedMiddleware = [] for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { // 最后一個中間件,next方法設置為Promise.resolve bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)) } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])) } }
最后我們就得到了一個next函數數組,也就是bindedMiddleware這個變量了。
Requesthttp.createServer中的回調函數,每次接收到請求的時候會被調用,所以我們在上面callback方法的TODO位置,編寫處理請求的代碼, 并將上面的middleware列表轉next函數列表的代碼放入其中。
function handleRequest(ctx, middlewares) { if (middlewares && middlewares.length > 0) { let bindedMiddleware = [] for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)) } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])) } } return bindedMiddleware[0]() } else { return Promise.resolve() } }Responce
我們簡單出來下相應就好了,直接將ctx.body發送給客戶端。
function handleResponse (ctx) { return function() { ctx.res.writeHead(200, { "Content-Type": "text/plain" }); ctx.res.end(ctx.body); } }完成Koa類的實現
koa的app實例上面帶有on,emit等方法,這是node events模塊實現好的東西。直接讓Koa類繼承自events模塊就好了。
我們再將上面實現出來的handleRequest和handleResponse方法放入koa類的callback方法中,得到最終我們實現的Koa,一共58行代碼,如下
const http = require("http"); const Emitter = require("events"); class Context { constructor(app, req, res) { this.app = app; this.req = req; this.res = res; } get set() { return this.res.setHeader } get method() { return this.req.method } get url() { return this.req.url } } class Koa extends Emitter{ constructor(options) { super(); this.options = options this.middlewares = []; } use(middleware) { this.middlewares.push(middleware); } callback() { return (req, res) => { let ctx = new Context(this, req, res); handleRequest(ctx, this.middlewares).then(handleResponse(ctx)); } } listen(port) { http.createServer(this.callback()).listen(port); } } function handleRequest(ctx, middlewares) { if (middlewares && middlewares.length > 0) { let bindedMiddleware = []; for (let i = middlewares.length - 1; i >= 0; i--) { if (middlewares.length == i + 1) { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve)); } else { bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0])); } } return bindedMiddleware[0](); } else { return Promise.resolve(); } } function handleResponse (ctx) { return function() { ctx.res.writeHead(200, { "Content-Type": "text/plain" }); ctx.res.end(ctx.body); } } module.exports = Koa;
試試跑一下篇首的Demo,沒什么問題。
結語簡版實現,碼糙理不糙,展示出了koa核心的東西,但少了錯誤處理,也完全沒有考慮性能啥的,需要完善的地方還很多很多。
筆者在寫了這個5分鐘koa以后去看了koa源碼,發現實現思路基本就是這樣,相信經過我的這個5分鐘koa的洗禮,你去看koa源碼一樣小菜一碟。
Done!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107910.html
摘要:時間時間時間每一份有限的時間里都飽含無限的價值需要非常珍惜開發環境需要的信息官方手冊,一款工具,具體內容請移步官方手冊官方手冊,一刻代碼包管理工具,具體內容請移步官方手冊官方安裝手冊,前端框架瀏覽器擴展,用于調試應用程序,下一篇進行詳細介 ArthurSlog SLog-6 Year·1 Guangzhou·China July 13th 2018 showImg(https://...
摘要:異步最佳實踐避免回調地獄前端掘金本文涵蓋了處理異步操作的一些工具和技術和異步函數。 Nodejs 連接各種數據庫集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 編寫 Node.js Rest API 的 10 個最佳實踐 - 前端 - 掘金全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自...
摘要:要對進行黑盒測試測試的最好辦法是對他們進行黑盒測試,黑盒測試是一種不關心應用內部結構和工作原理的測試方法,測試時系統任何部分都不應該被。此外,有了黑盒測試并不意味著不需要單元測試,針對的單元測試還是需要編寫的。 本文首發于之乎專欄前端周刊,全文共 6953 字,讀完需 8 分鐘,速度需 2 分鐘。翻譯自:RingStack 的文章 https://blog.risingstack.co...
摘要:優點參考維基與對比圖客戶端例子連接成功后調用當接收到服務器消息時調用連接關閉后調用服務端例子運行結果客戶端服務端名詞解釋握手一般創建鏈接需要通過瀏覽器發出請求服務器做出回應這個過程稱為握手參考鏈接協議分鐘從入門到精通 原文地址 github項目地址 1. 什么是WebSocket? WebSocket是一種在單個TCP連接上進行全雙工通信的協議。 使得客戶端和服務器之間的數據交換變...
閱讀 1833·2021-09-22 15:23
閱讀 3255·2021-09-04 16:45
閱讀 1842·2021-07-29 14:49
閱讀 2767·2019-08-30 15:44
閱讀 1523·2019-08-29 16:36
閱讀 1037·2019-08-29 11:03
閱讀 1504·2019-08-26 13:53
閱讀 504·2019-08-26 11:57