成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

5分鐘實(shí)現(xiàn)一個(gè)Koa

Panda / 515人閱讀

摘要:所以方法實(shí)現(xiàn)如下每次有新的請(qǐng)求,我們都需要把這次請(qǐng)求的上下文灌進(jìn)數(shù)組中的每一個(gè)中間件里。筆者在寫了這個(gè)分鐘以后去看了源碼,發(fā)現(xiàn)實(shí)現(xiàn)思路基本就是這樣,相信經(jīng)過(guò)我的這個(gè)分鐘的洗禮,你去看源碼一樣小菜一碟。

原文地址

周五組內(nèi)同學(xué)討論搞一些好玩的東西,有人提到了類似『5分鐘實(shí)現(xiàn)koa』,『100行實(shí)現(xiàn)react』的創(chuàng)意,仔細(xì)想了以后,5分鐘實(shí)現(xiàn)koa并非不能實(shí)現(xiàn),遂有了這篇博客。
準(zhǔn)備

先打開koa官網(wǎng),隨意找出了一個(gè)代表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);

最終要實(shí)現(xiàn)的效果是實(shí)現(xiàn)的一個(gè)5min-koa模塊,直接將代碼中第一行替換為const Koa = require("./5min-koa");,程序可以正常執(zhí)行就可以了。

Koa的核心

通過(guò)koa官網(wǎng)得知,app.listen方法實(shí)際上是如下代碼的簡(jiǎn)寫

const http = require("http");
const Koa = require("koa");
const app = new Koa();
http.createServer(app.callback()).listen(3000);

所以我們可以先把a(bǔ)pp.listen實(shí)現(xiàn)出來(lái)

class Koa {
  constructor() {}
  callback() {
    return (req, res) => {
      // TODO
    }
  }
  listen(port) {
    http.createServer(this.callback()).listen(port);
  }
}

koa的核心分為四部分,分別是

context 上下文

middleware 中間件

request 請(qǐng)求

responce 響應(yīng)

Context

我們先來(lái)實(shí)現(xiàn)一個(gè)最簡(jiǎn)化版的context,如下

class Context {
  constructor(app, req, res) {
    this.app = app
    this.req = req
    this.res = res
    // 為了盡可能縮短實(shí)現(xiàn)時(shí)間,我們直接使用原生的res和req,沒(méi)有實(shí)現(xiàn)ctx上的ctx.request ctx.response
    // ctx.request ctx.response只是在原生res和req上包裝處理了一層
  }
  // 實(shí)現(xiàn)一些demo中使用到的ctx上代理的方法
  get set() { return this.res.setHeader }
  get method() { return this.req.method }
  get url() { return this.req.url }
}

這樣就完成了一個(gè)最基本的Context,別看小,已經(jīng)夠用了。
每一次有新的請(qǐng)求,都會(huì)創(chuàng)建一個(gè)新的ctx對(duì)象。

Middleware

koa的中間件是一個(gè)異步函數(shù),接受兩個(gè)參數(shù),分別是ctx和next,其中ctx是當(dāng)前的請(qǐng)求上下文,next是下一個(gè)中間件(也是異步函數(shù)),這樣想來(lái),我們需要一個(gè)維護(hù)中間件的數(shù)組,每次調(diào)用app.use就是往數(shù)組中push一個(gè)一步函數(shù)。所以u(píng)se方法實(shí)現(xiàn)如下

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

每次有新的請(qǐng)求,我們都需要把這次請(qǐng)求的上下文灌進(jìn)數(shù)組中的每一個(gè)中間件里。單單灌進(jìn)ctx還不夠,還要使每個(gè)中間件都能通過(guò)next函數(shù)調(diào)用到下一個(gè)中間件。當(dāng)我們調(diào)用next函數(shù)時(shí),一般是不需要傳參數(shù)的,而被調(diào)用的中間件中一定會(huì)接收到ctx和next兩個(gè)參數(shù)。

調(diào)用方不需要傳參,被調(diào)用方卻能接到參數(shù),這讓我立刻想到bind方法,只要將每一個(gè)中間件所需要的ctx和next都提前綁定好,問(wèn)題就解決了。下面的代碼就是通過(guò)bind方法,將用戶傳入的middleware列表轉(zhuǎn)換成next函數(shù)列表

let bindedMiddleware = []

for (let i = middlewares.length - 1; i >= 0; i--) {
  if (middlewares.length == i + 1) {
    // 最后一個(gè)中間件,next方法設(shè)置為Promise.resolve
    bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, Promise.resolve))
  } else {
    bindedMiddleware.unshift(middlewares[i].bind(ctx.app, ctx, bindedMiddleware[0]))
  }
}

最后我們就得到了一個(gè)next函數(shù)數(shù)組,也就是bindedMiddleware這個(gè)變量了。

Request

http.createServer中的回調(diào)函數(shù),每次接收到請(qǐng)求的時(shí)候會(huì)被調(diào)用,所以我們?cè)谏厦鎐allback方法的TODO位置,編寫處理請(qǐng)求的代碼, 并將上面的middleware列表轉(zhuǎn)next函數(shù)列表的代碼放入其中。

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

我們簡(jiǎn)單出來(lái)下相應(yīng)就好了,直接將ctx.body發(fā)送給客戶端。

function handleResponse (ctx) {
  return function() {
    ctx.res.writeHead(200, { "Content-Type": "text/plain" });
    ctx.res.end(ctx.body);
  }
}
完成Koa類的實(shí)現(xiàn)

koa的app實(shí)例上面帶有on,emit等方法,這是node events模塊實(shí)現(xiàn)好的東西。直接讓Koa類繼承自events模塊就好了。
我們?cè)賹⑸厦鎸?shí)現(xiàn)出來(lái)的handleRequest和handleResponse方法放入koa類的callback方法中,得到最終我們實(shí)現(xiàn)的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,沒(méi)什么問(wèn)題。

結(jié)語(yǔ)

簡(jiǎn)版實(shí)現(xiàn),碼糙理不糙,展示出了koa核心的東西,但少了錯(cuò)誤處理,也完全沒(méi)有考慮性能啥的,需要完善的地方還很多很多。

筆者在寫了這個(gè)5分鐘koa以后去看了koa源碼,發(fā)現(xiàn)實(shí)現(xiàn)思路基本就是這樣,相信經(jīng)過(guò)我的這個(gè)5分鐘koa的洗禮,你去看koa源碼一樣小菜一碟。

Done!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107910.html

相關(guān)文章

  • Slog6_使用vue前端框架實(shí)現(xiàn)單頁(yè)應(yīng)用(SPA)

    摘要:時(shí)間時(shí)間時(shí)間每一份有限的時(shí)間里都飽含無(wú)限的價(jià)值需要非常珍惜開發(fā)環(huán)境需要的信息官方手冊(cè),一款工具,具體內(nèi)容請(qǐng)移步官方手冊(cè)官方手冊(cè),一刻代碼包管理工具,具體內(nèi)容請(qǐng)移步官方手冊(cè)官方安裝手冊(cè),前端框架瀏覽器擴(kuò)展,用于調(diào)試應(yīng)用程序,下一篇進(jìn)行詳細(xì)介 ArthurSlog SLog-6 Year·1 Guangzhou·China July 13th 2018 showImg(https://...

    FingerLiu 評(píng)論0 收藏0
  • node技術(shù)棧 - 收藏集 - 掘金

    摘要:異步最佳實(shí)踐避免回調(diào)地獄前端掘金本文涵蓋了處理異步操作的一些工具和技術(shù)和異步函數(shù)。 Nodejs 連接各種數(shù)據(jù)庫(kù)集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 編寫 Node.js Rest API 的 10 個(gè)最佳實(shí)踐 - 前端 - 掘金全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自...

    王偉廷 評(píng)論0 收藏0
  • 編寫 Node.js Rest API 的 10 個(gè)最佳實(shí)踐

    摘要:要對(duì)進(jìn)行黑盒測(cè)試測(cè)試的最好辦法是對(duì)他們進(jìn)行黑盒測(cè)試,黑盒測(cè)試是一種不關(guān)心應(yīng)用內(nèi)部結(jié)構(gòu)和工作原理的測(cè)試方法,測(cè)試時(shí)系統(tǒng)任何部分都不應(yīng)該被。此外,有了黑盒測(cè)試并不意味著不需要單元測(cè)試,針對(duì)的單元測(cè)試還是需要編寫的。 本文首發(fā)于之乎專欄前端周刊,全文共 6953 字,讀完需 8 分鐘,速度需 2 分鐘。翻譯自:RingStack 的文章 https://blog.risingstack.co...

    ermaoL 評(píng)論0 收藏0
  • 學(xué)習(xí)WebSocket(附: WebSocket + koa例子)

    摘要:優(yōu)點(diǎn)參考維基與對(duì)比圖客戶端例子連接成功后調(diào)用當(dāng)接收到服務(wù)器消息時(shí)調(diào)用連接關(guān)閉后調(diào)用服務(wù)端例子運(yùn)行結(jié)果客戶端服務(wù)端名詞解釋握手一般創(chuàng)建鏈接需要通過(guò)瀏覽器發(fā)出請(qǐng)求服務(wù)器做出回應(yīng)這個(gè)過(guò)程稱為握手參考鏈接協(xié)議分鐘從入門到精通 原文地址 github項(xiàng)目地址 1. 什么是WebSocket? WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議。 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變...

    0xE7A38A 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<